pax_global_header00006660000000000000000000000064122270003550014506gustar00rootroot0000000000000052 comment=589a0740533855109efd3d97af1f87ca9f40f61e clsync-0.2.1/000077500000000000000000000000001222700035500130015ustar00rootroot00000000000000clsync-0.2.1/.doxygen000066400000000000000000002353441222700035500144720ustar00rootroot00000000000000# 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 # 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 = YES # 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 = YES # 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.2.1/.gitignore000066400000000000000000000012151222700035500147700ustar00rootroot00000000000000# dynamical headers: /revision.h # objects: *.o # linked: /clsync /clsync-debug # other: test.c build test test0 test1 test2 .gdbcommands log* from/ to/ .clsync-list.* /debian/clsync/ /debian/clsync.debhelper.log /debian/clsync.substvars /debian/files /debian/*.ex /debian/*.debhelper /examples/rules /examples/testdir/ /examples/*.so /doc/ *.swp # autotools build debris /.deps/ /aclocal.m4 /autom4te.cache/ /config.h* /config.log /config.status /configure /depcomp /install-sh /missing /stamp-h1 Makefile Makefile.in /debian/autoreconf.after /debian/autoreconf.before /autoscan.log /configure.scan # coverage test files /*.gcda /*.gcno /*.gcov clsync-0.2.1/.travis.sh000077500000000000000000000062301222700035500147270ustar00rootroot00000000000000#!/bin/bash # configuration TIMEOUT_SYNC=15 # test aggressive optimizations export 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 $@ && make -j5 || { echo "!!! test with \"@\" configure options failed" exit 1 } } # Cleanup functions for run_example() run_example_cleanup_success() { pkill -F "$CLSYNC_PIDFILE" } run_example_cleanup_failure() { pkill -F "$CLSYNC_PIDFILE" exit 1 } # Run example script run_example() { MODE="$1" export CLSYNC_PIDFILE="/tmp/clsync-example-$MODE.pid" CONFIGFILE="/tmp/clsync-example-$MODE.conf" rm -rf "examples/testdir"/{to,from}/* mkdir -p "examples/testdir/to" trap run_example_cleanup_failure INT TERM touch "$CONFIGFILE" cd examples bash -x clsync-start-"$MODE".sh --background --pid-file "$CLSYNC_PIDFILE" --config-file "$CONFIGFILE" -w1 -t1 cd - rm -f "$CONFIGFILE" 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,b,c,d,e,f,g,h} touch examples/testdir/from/a/b/c/d/e/f/g/h/a 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/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" -a -d "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" ]; then sleep 1 break fi sleep 1 (( i++ )) done if [ "$i" -gt "$TIMEOUT_SYNC" ]; then run_example_cleanup_failure fi if ! [ -f "$CLSYNC_PIDFILE" ]; then run_example_cleanup_failure 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 fi if [ -f "examples/testdir/to/file" -a ! -d "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" ]; then sleep 1 run_example_cleanup_success return fi sleep 1 (( i++ )) done run_example_cleanup_failure } # Test all possible package-specific configure options. # Do not test empty cases save as no options at all. build_test ${arg[@]} for a0 in "--enable-cluster" "--disable-cluster"; do arg[0]="$a0" for a1 in "--enable-debug" "--disable-debug"; do arg[1]="$a1" for a2 in "--enable-paranoid=0" "--enable-paranoid=1" "--enable-paranoid=2" ; do arg[2]="$a2" for a3 in "--with-capabilities" "--without-capabilities"; do arg[3]="$a3" for a4 in "--with-mhash" "--without-mhash"; do arg[4]="$a4" #build_test ${arg[@]} done done done done done # Test coverage export CFLAGS="$CFLAGS --coverage -O0" export PATH="$(pwd):$PATH" build_test --enable-cluster --enable-debug --enable-paranoid=2 --with-capabilities --without-mhash run_example rsyncdirect run_example rsyncshell #run_example rsyncso #run_example so #run_example cluster exit 0 clsync-0.2.1/.travis.yml000066400000000000000000000004571222700035500151200ustar00rootroot00000000000000language: c before_install: - sudo apt-get install libcap-dev libglib2.0-dev libmhash-dev - sudo pip install cpp-coveralls --use-mirrors script: - ./.travis.sh after_success: - coveralls --exclude examples --exclude debian --exclude gentoo --exclude autom4te.cache --exclude man --exclude conf*.dir clsync-0.2.1/CONTRIB000066400000000000000000000007421222700035500140270ustar00rootroot00000000000000 I want to say thanks to next people: 1. Andrew A Savchenko 0x76B176E4 For adapting clsync for gentoo and for good programming advices :) 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.2.1/DEVELOPING000066400000000000000000000056401222700035500143650ustar00rootroot00000000000000 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.2.1/LICENSE000066400000000000000000000020321222700035500140030ustar00rootroot00000000000000Format: 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.2.1/Makefile.am000066400000000000000000000017371222700035500150450ustar00rootroot00000000000000bin_PROGRAMS = clsync clsync_SOURCES = sync.c cluster.c main.c output.c fileutils.c malloc.c glibex.c main.o: revision.h man_MANS = man/man1/clsync.1 doc_DATA = CONTRIB DEVELOPING LICENSE PROTOCOL README.md TODO exampledir=$(docdir)/examples example_DATA = \ $(wildcard $(srcdir)/examples/*.c) \ $(wildcard $(srcdir)/examples/*.sh) clsync_includedir = $(includedir)/clsync clsync_include_HEADERS = configuration.h clsync.h malloc.h indexes.h options.h output.h revision.h: (echo -n '#define REVISION "'; [ -d .git ] && \ (echo -n '.'$$(( $$(git log 2>/dev/null | grep -c ^commit | tr -d "\n") - 523 )) ) \ || echo -n '-release'; echo '"') > $@ doc: doxygen .doxygen install-data-local: mkdir -p $(DESTDIR)/$(exampledir) cp -R examples/production $(DESTDIR)/$(exampledir)/ uninstall-local: rm -rf $(DESTDIR)/$(exampledir)/production CLEANFILES = revision.h CLEANFILES += examples/rules clean-local: -rm -rf examples/testdir examples/*.o examples/*.so examples/*.xz doc clsync-0.2.1/NOTES000066400000000000000000000012331222700035500136130ustar00rootroot00000000000000v0.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.2.1/PROTOCOL000066400000000000000000000034631222700035500141730ustar00rootroot00000000000000Unfortunetaly, 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 -> getmyid (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 -> getmyid (serial: 0; src: NOID ; dst: NOID; name: B) | cluster_init() A -> setid (serial: 3; src: 0 ; dst: NOID; name: A; updatets: 100) 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 -> getmyid (serial: 0; src: NOID ; dst: NOID; name: A) B -> setid (serial: 4; src: 1 ; dst: 0; name: A; updatets: 200) A -> register (serial: 1; src: 0 ; dst: NOID; name: A) B -> ack (serial: 5; src: 1 ; dst: 0; ack_serial: 1) [...] clsync-0.2.1/README.md000066400000000000000000000204141222700035500142610ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/xaionaro/clsync.png?branch=master)](https://travis-ci.org/xaionaro/clsync) [![Coverage Status](https://coveralls.io/repos/xaionaro/clsync/badge.png)](https://coveralls.io/r/xaionaro/clsync) clsync ====== Contents -------- 1. Name 2. Motivation 3. inotify vs fanotify 4. Installing 5. How to use 6. Example of usage 7. Clustering 8. Known building issues 9. Support 10. Developing 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 was been renamed to "fasync". After that I started to intensively write the program. However 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" project ("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 multicast notifing subsystem to prevent loops on bidirection syncing. So "clsync" also can be interpreted as "cluster live sync". ;) 2. Motivation ------------- This utility was been writted for two purposes: - for making failover clusters - for making backups of them To do failover 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. Currently we are 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. Sorry, if I'm wrong. Let me know if it is, please :). "lsyncd" - is really good and useful utility, just it's not appropriate for us. UPD.: Also clsync was 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. 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 :) 8. 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 9. 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 10. Developing -------------- I started to write "DEVELOPING" and "PROTOCOL" files. You can look there if you wish. ;) I'll be glad to receive code contribution :) -- Dmitry Yu Okunev 0x8E30679C clsync-0.2.1/TODO000066400000000000000000000011361222700035500134720ustar00rootroot000000000000000! [SECURITY] Drop privilegies. Preserve access to files via "capabilites". 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.2.1/clsync.h000066400000000000000000000062671222700035500144600ustar00rootroot00000000000000/* 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 #include #include #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 }; 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 options; struct indexes; typedef int(*api_funct_init) (struct options *, 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] options_p Pointer to "options" * * @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 options *options_p); clsync-0.2.1/cluster.c000066400000000000000000001304321222700035500146310ustar00rootroot00000000000000/* 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 . */ /* Hello, dear developer. Cluster technologies are almost always very difficult. So I'll try to fill this code with comments. Enjoy ;) Also you can ask me directly by e-mail or IRC, if something seems too hard. -- 0x8E30679C */ #ifdef CLUSTER_SUPPORT #include "common.h" #include "cluster.h" #include "sync.h" #include "output.h" #include "malloc.h" #ifdef HAVE_MHASH #include #endif // Global variables. They will be initialized in cluster_init() #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}; options_t *options_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) { printf_e("Error: clustercmd_window_add(): Invalid src_node_id: %i.\n", 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); } printf_ddd("Debug3: clustercmd_window_add(): w.size == %u, b_left == %u; b_right == %u; w.buf_size == %u; r_space == %u\n", 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; } printf_ddd("Debug3: clustercmd_window_add(): 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; printf_ddd("Debug3: clustercmd_window_add(): b_coord == %u\n", 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) { printf_e("Error: clustercmd_window_del(): window not allocated.\n"); return EINVAL; } if(!window_p->packets_len) { printf_e("Error: clustercmd_window_del(): there already no packets in the window.\n"); 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) { printf_ddd("Debug3: clustercmd_window_del(): %i -> %i\n", 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; } #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(unsigned char *data, int32_t len) { // where data is the location of the data in physical // memory and len is the length of the data in bytes 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) { a = (a + data[index]) % MOD_ADLER; b = (b + a) % MOD_ADLER; } return (b << 16) | a; } #endif /** * @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) { if(flags & ADLER32_CALC_HEADER) { uint32_t adler32; clustercmdadler32_t adler32_save; // Preparing memcpy(&adler32_save, &clustercmd_p->h.adler32, sizeof(clustercmdadler32_t)); memset(&clustercmd_p->h.adler32, 0, sizeof(clustercmdadler32_t)); adler32 = 0xFFFFFFFF; uint32_t size = sizeof(clustercmdhdr_t); char *ptr = (char *)&clustercmd_p->h; // Calculating #ifdef HAVE_MHASH MHASH td = mhash_init(MHASH_ADLER32); mhash(td, ptr, size); mhash_deinit(td, &adler32); #else adler32 = adler32_calc((unsigned char *)ptr, size); #endif // Ending memcpy(&clustercmd_p->h.adler32, &adler32_save, sizeof(clustercmdadler32_t)); clustercmdadler32_p->hdr = adler32 ^ 0xFFFFFFFF; } if(flags & ADLER32_CALC_DATA) { uint32_t adler32; uint32_t size = clustercmd_p->h.data_len; char *ptr = clustercmd_p->data.p; #ifdef PARANOID if(size & 0x3) { printf_e("Error: clustercmd_adler32_calc(): clustercmd_p->h.data_len&0x3 != 0: %u\n", clustercmd_p->h.data_len); return EINVAL; } #endif // Calculating #ifdef HAVE_MHASH MHASH td = mhash_init(MHASH_ADLER32); mhash(td, ptr, size); mhash_deinit(td, &adler32); #else adler32 = adler32_calc((unsigned char *)ptr, size); #endif // Ending clustercmdadler32_p->dat = adler32 ^ 0xFFFFFFFF; } return 0; } /** * @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_status_change(uint8_t node_id, uint8_t 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->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: node_online++; break; default: if(node_status == NODESTATUS_OFFLINE) node_online--; break; } nodeinfo[node_id].status = node_status; 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) { clustercmd_p->h.src_node_id = node_id_my; clustercmd_adler32_calc(clustercmd_p, &clustercmd_p->h.adler32, ADLER32_CALC_ALL); printf_ddd("Debug3: cluster_send(): Sending: " "{h.dst_node_id: %u, h.src_node_id: %u, cmd_id: %u, adler32.hdr: %p, adler32.dat: %p, data_len: %u}\n", 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: printf_d("Debug: cluster_send(): There's no online node with id %u. Skipping sending.\n", clustercmd_p->h.dst_node_id); return EADDRNOTAVAIL; default: break; } // Putting the message into output windowa if(nodeinfo_my != NULL) clustercmd_window_add(&window_o, clustercmd_p, nodeinfo_my->serial2queuedpacket_ht); // Sending the message sendto(sock_o, clustercmd_p, CLUSTERCMD_SIZE_PADDED(clustercmd_p), 0, &sa_o, sizeof(sa_o)); // Finishing 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[in] buf Pointer to buffer * @param[in] size Amount of bytes to read * * @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, void *buf, size_t size, cluster_read_flags_t flags) { static struct in_addr last_addr = {0}; struct sockaddr_in sa_in; size_t sa_in_len = sizeof(sa_in); int readret = recvfrom(sock, buf, size, MSG_WAITALL, (struct sockaddr *)&sa_in, (socklen_t * restrict)&sa_in_len); if(flags & CLREAD_CONTINUE) { if(memcmp(&last_addr, &sa_in.sin_addr, sizeof(last_addr))) { printf_d("Debug: Get message from wrong source (%s != %s). Skipping it :(.\n", inet_ntoa(sa_in.sin_addr), inet_ntoa(last_addr)); size = 0; return 0; } } memcpy(&last_addr, &sa_in.sin_addr, sizeof(last_addr)); #ifdef PARANOID if(!readret) { printf_e("Error: cluster_read(): recvfrom() returned 0. This shouldn't happend. Exit."); return EINVAL; } #endif if(readret < 0) { printf_e("Error: cluster_read(): recvfrom() returned %i. " "Seems, that something wrong with network socket: %s (errno %i).\n", readret, strerror(errno), errno); return errno != -1 ? errno : -2; } printf_dd("Debug2: cluster_read(): Got message from %s (len: %i, expected: %i).\n", inet_ntoa(sa_in.sin_addr), readret, size); if(readret < size) { // Too short message printf_e("Warning: cluster_read(): Got too short message from node. Ignoring it.\n"); return -1; } 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/o] timeout_p Pointer to timeout (in milliseconds). Timeout is assumed zero if the pointer is NULL. After waiting the event timeout value will be decreased on elapsed time. * * @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, unsigned int *timeout_p) { static clustercmd_t *clustercmd_p=NULL; static size_t size=0; static uint8_t last_src_node_id = NODEID_NOID; static uint32_t last_serial = 0; int timeout; // Getting the timeout timeout = (timeout_p == NULL ? 0 : *timeout_p); if(!size) { size = BUFSIZ; clustercmd_p = (clustercmd_t *)xmalloc(size); } // 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]; 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() struct timeval tv; fd_set rfds; FD_ZERO(&rfds); FD_SET(sock_i, &rfds); tv.tv_sec = timeout / 1000; tv.tv_usec = timeout % 1000; int selret = select(sock_i+1, &rfds, NULL, NULL, &tv); // Remembering the rest part of timeout if(timeout_p != NULL) *timeout_p = tv.tv_sec * 1000 + tv.tv_usec / 1000; // processing select()'s retuned value if(selret < 0) { printf_e("Error: cluster_recv(): got error while select(): %s (errno: %i).\n", strerror(errno), errno); return 0; } if(selret == 0) { printf_ddd("Debug: cluster_recv(): no new messages.\n"); return 0; } printf_ddd("Debug: cluster_recv(): got new message(s).\n"); // 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, (void *)clustercmd_p, sizeof(clustercmdhdr_t), CLREAD_NONE))) { if(ret == -1) return 0; // Invalid message? Skipping. printf_e("Error: cluster_recv(): Got error from cluster_read(): %s (errno %i).\n", strerror(errno), errno); errno = ret; return -1; } // Checking adler32 of packet headers. clustercmd_adler32_calc(clustercmd_p, &adler32, ADLER32_CALC_HEADER); if(adler32.hdr != clustercmd_p->h.adler32.hdr) { printf_d("Debug: cluster_recv(): hdr-adler32 mismatch: %p != %p.\n", (void*)(long)clustercmd_p->h.adler32.hdr, (void*)(long)adler32.hdr); if((ret=clustercmd_reject(clustercmd_p, REJ_adler32MISMATCH)) != EADDRNOTAVAIL) { printf_e("Error: cluster_recv(): Got error while clustercmd_reject(): %s (errno: %i).\n", strerror(ret), ret); 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_GETMYID) { printf_e("Warning: cluster_recv(): Got non getmyid packet from NOID node. Ignoring the packet.\n"); return 0; } if(clustercmd_p->h.serial != 0) { printf_e("Warning: cluster_recv(): Got packet with non-zero serial from NOID node. Ignoring the packet.\n"); return 0; } } else // Wrong src_node_id? if(src_node_id >= MAXNODES) { printf_e("Warning: cluster_recv(): Invalid h.src_node_id: %i >= "XTOSTR(MAXNODES)"\n", src_node_id); return 0; } // Is this broadcast message? if(dst_node_id == NODEID_NOID) { // CODE HERE } else // Wrong dst_node_id? if(dst_node_id >= MAXNODES) { printf_e("Warning: cluster_recv(): Invalid h.dst_node_id: %i >= "XTOSTR(MAXNODES)"\n", dst_node_id); return 0; } // Seems, that headers are correct. Continuing. printf_ddd("Debug3: cluster_recv(): Received: {h.dst_node_id: %u, h.src_node_id: %u, cmd_id: %u," " adler32: %u, data_len: %u}, timeout: %u -> %u\n", dst_node_id, src_node_id, clustercmd_p->h.cmd_id, clustercmd_p->h.adler32, clustercmd_p->h.data_len, *timeout_p, timeout); // Paranoid routines // The message from us? Something wrong if it is. #ifdef PARANOID if((clustercmd_p->h.src_node_id == node_id_my) && (node_id_my != NODEID_NOID)) { #ifdef VERYPARANOID printf_e("Error: cluster_recv(): clustercmd_p->h.src_node_id == node_id_my (%i != %i)." " Exit.\n", clustercmd_p->h.src_node_id, node_id_my); return EINVAL; #else printf_e("Warning: cluster_recv(): clustercmd_p->h.src_node_id == node_id_my (%i != %i)." " Ignoring the command.\n", clustercmd_p->h.src_node_id, node_id_my); clustercmd_p = NULL; return 0; #endif } #endif nodeinfo_t *nodeinfo_p = &nodeinfo[src_node_id]; // Not actual packet? if(clustercmd_p->h.serial <= nodeinfo_p->last_serial) { printf_d("Debug: cluster_recv(): Ignoring packet from %i due to serial: %i <= %i\n", src_node_id, clustercmd_p->h.serial, nodeinfo_p->last_serial); return 0; } // 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 0; } // 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) { printf_e("Warning: cluster_recv(): Got too big message from node %i. Ignoring it.\n", src_node_id); return 0; } // Incorrect size of data? if(clustercmd_p->h.data_len & 0x3) { printf_e("Warning: cluster_recv(): Received packet of size not a multiple of 4. Ignoring it.\n"); return 0; } // 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); } // Reading the data if((ret=cluster_read(sock_i, (void *)clustercmd_p->data.p, clustercmd_p->h.data_len, CLREAD_CONTINUE))) { if(ret == -1) return 0; printf_e("Error: cluster_recv(): Got error from cluster_read(): %s (errno %i).\n", strerror(errno), errno); 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) { printf_d("Debug: cluster_recv(): dat-adler32 mismatch: %p != %p.\n", (void*)(long)clustercmd_p->h.adler32.dat, (void*)(long)adler32.dat); if((ret=clustercmd_reject(clustercmd_p, REJ_adler32MISMATCH)) != EADDRNOTAVAIL) { printf_e("Error: cluster_recv(): Got error while clustercmd_reject(): %s (errno: %i).\n", strerror(ret), ret); errno = ret; return -1; } } CLUSTER_RECV_RETURNMESSAGE(clustercmd_p); } /** * @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) { printf_ddd("Debug3: cluster_recv_proc(%i)\n", _timeout); clustercmd_t *clustercmd_p; int ret; unsigned int timeout = _timeout; while((ret=cluster_recv(&clustercmd_p, &timeout))) { // Exit if error if(ret == -1) { printf_e("Error: cluster_recv_proc(): Got error while cluster_recv(): %s (%i).\n", strerror(errno), errno); return errno; } // If we have appropriate callback function, then call it! :) if(recvproc_funct[clustercmd_p->h.cmd_id]) if((ret=recvproc_funct[clustercmd_p->h.cmd_id](clustercmd_p))) { printf_e("Error: cluster_recv_proc(): Got error from recvproc_funct[%i]: %s (%i)\n", clustercmd_p->h.cmd_id, strerror(ret), ret); return ret; } } return 0; } /** * @brief recvproc-function for ACK-messages * * @param[in] clustercmd_p Pointer to clustercmd * @param[in] arg_p Pointer to argument * * @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); 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) { printf_e("Error: cluster_recv_proc_deinit(): window_i.buf_size != 0, but window_i.buf == NULL.\n"); } else #endif free(window_i.buf); } if(window_o.buf_size) { #ifdef PARANOID if(window_o.buf == NULL) { printf_e("Error: cluster_recv_proc_deinit(): window_o.buf_size != 0, but window_o.buf == NULL.\n"); } else #endif free(window_o.buf); } return 0; } /** * @brief recvproc-function for setid-messages * * @param[in] clustercmd_p Pointer to clustercmd * @param[in] arg_p Pointer to argument * * @retval zero Successfully initialized. * @retval non-zero Got error, while initializing. * */ static int cluster_recvproc_setid(clustercmd_t *clustercmd_p) { static time_t updatets = 0; // Is this the most recent information? Skipping if not. clustercmd_setiddata_t *data_setid_p = &clustercmd_p->data.setid; if(!(data_setid_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 = CLUSTER_RESTDATALEN(clustercmd_p, clustercmd_setiddata_t); if(recv_nodename_len != options_p->cluster_nodename_len) return 0; // Is the node name equals to ours? Skipping if not. if(memcmp(data_setid_p->node_name, options_p->cluster_nodename, recv_nodename_len)) return 0; // Remembering the node that answered us node_status_change(clustercmd_p->h.src_node_id, NODESTATUS_SEEMSONLINE); // Seems, that somebody knows our node id, remembering it. node_id_my = clustercmd_p->h.dst_node_id; updatets = data_setid_p->updatets; return 0; } extern int cluster_loop(); /** * @brief Initializes cluster subsystem. * * @param[in] _options_p Pointer to "options" 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(options_t *_options_p, indexes_t *_indexes_p) { int ret; // Preventing double initializing if(options_p != NULL) { printf_e("Error: cluster_init(): cluster subsystem is already initialized.\n"); return EALREADY; } // Initializing global variables, pt. 1 options_p = _options_p; indexes_p = _indexes_p; cluster_timeout = options_p->cluster_timeout; node_status_change(NODEID_NOID, NODESTATUS_ONLINE); // Initializing network routines // Input socket // Creating socket sock_i = socket(AF_INET, SOCK_DGRAM, 0); if(sock_i < 0) { printf_e("cluster_init(): Cannot create socket for input traffic: %s (errno: %i)\n", strerror(errno), errno); 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) { printf_e("Error: cluster_init(): Got error while setsockopt(): %s (errno: %i)\n", strerror(errno), errno); return errno; } // Binding sa_i.sin_family = AF_INET; sa_i.sin_port = htons(options_p->cluster_mcastipport); sa_i.sin_addr.s_addr = INADDR_ANY; if(bind(sock_i, (struct sockaddr*)&sa_i, sizeof(sa_i))) { printf_e("Error: cluster_init(): Got error while bind(): %s (errno: %i)\n", strerror(errno), errno); return errno; } // Joining to multicast group struct ip_mreq group; group.imr_interface.s_addr = inet_addr(options_p->cluster_iface); group.imr_multiaddr.s_addr = inet_addr(options_p->cluster_mcastipaddr); if(setsockopt(sock_i, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0) { printf_e("Error: cluster_init(): Cannot setsockopt() to enter to membership %s -> %s\n", options_p->cluster_iface, options_p->cluster_mcastipaddr); return errno; } // Output socket // Creating socket sock_o = socket(AF_INET, SOCK_DGRAM, 0); if(sock_o < 0) { printf_e("cluster_init(): Cannot create socket for output traffic: %s (errno: %i)\n", strerror(errno), errno); return errno; } // Initializing the group sockaddr structure sa_o.sin_family = AF_INET; sa_o.sin_port = htons(options_p->cluster_mcastipport); sa_o.sin_addr.s_addr = inet_addr(options_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) { printf_e("Error: Cannot disable loopback for output socket: %s (errno: %i).\n", strerror(errno), errno); return errno; } } // Setting local interface for output traffic { struct in_addr addr_o; addr_o.s_addr = inet_addr(options_p->cluster_iface); if(setsockopt(sock_o, IPPROTO_IP, IP_MULTICAST_IF, &addr_o, sizeof(addr_o)) < 0) { printf_e("Error: Cannot set local interface for outbound traffic: %s (errno: %i)\n", strerror(errno), errno); 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 { clustercmd_t *clustercmd_p = CLUSTER_ALLOCA(clustercmd_getmyid_t, options_p->cluster_nodename_len); clustercmd_p->h.data_len = options_p->cluster_nodename_len; memcpy(clustercmd_p->data.getmyid.node_name, options_p->cluster_nodename, clustercmd_p->h.data_len+1); clustercmd_p->h.cmd_id = CLUSTERCMDID_GETMYID; clustercmd_p->h.dst_node_id = NODEID_NOID; // broadcast if((ret=cluster_send(clustercmd_p))) return ret; } // Processing answers cluster_recv_proc_set(CLUSTERCMDID_SETID, cluster_recvproc_setid); if((ret=cluster_recv_proc(cluster_timeout))) return ret; printf_ddd("Debug3: cluster_init(): After communicating with others, my node_id is %i.\n", 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, options_p->cluster_nodename, options_p->cluster_nodename_len+1); clustercmd_p->h.data_len = options_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; } // Getting answers if((ret=cluster_recv_proc(cluster_timeout))) return ret; node_status_change(node_id_my, NODESTATUS_ONLINE); // Initializing global variables, pt. 2 nodeinfo_my = &nodeinfo[node_id_my]; // 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_status_change(NODEID_NOID, NODESTATUS_DOESNTEXIST); #ifdef VERYPARANOID int i=0; #endif while(node_count) { #ifdef VERYPARANOID if(i++ > MAXNODES) { printf_e("Error: cluster_deinit() looped. Forcing break."); break; } #endif node_status_change(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(); 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 printf_ddd("Debug3: cluster_loop() started.\n"); while(1) { int _ret; // Waiting for event fd_set rfds; FD_ZERO(&rfds); FD_SET(sock_i, &rfds); printf_ddd("Debug3: cluster_loop(): select()\n"); _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 printf_ddd("Debug3: cluster_loop(): sigpending()\n"); if(sigpending(&sigset_cluster)) if(sigismember(&sigset_cluster, SIGTERM)) break; // Processing new messages printf_ddd("Debug3: cluster_loop(): cluster_recv_proc()\n"); if((ret=cluster_recv_proc(0))) { sync_term(ret); break; } } printf_ddd("Debug3: cluster_loop() finished with exitcode %i.\n", 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 - options_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 > options_p->cluster_scan_dl_max) || (dirlevel_rel < options_p->cluster_hash_dl_min)) return 0; // Getting directory/file-'s information (including "change time" aka "st_ctime") struct stat64 stat64; ret=lstat64(path, &stat64); if(ret) { printf_e("Error: cluster_modtime_update() cannot lstat64() on \"%s\": %s (errno: %i)\n", path, strerror(errno), errno); 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[options_p->watchdirlen]; size_t dirpath_rel_full_len = dirpath_len - options_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 >= options_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.2.1/cluster.h000066400000000000000000000160411222700035500146350ustar00rootroot00000000000000/* 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 . */ #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) { \ printf_e("Error: CLUSTER_LOOP_EXPECTCMD(): Got error while cluster_recv(): %s (%i).\n", strerror(errno), errno); \ 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_CONTINUE = 0x01, 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; }; typedef struct nodeinfo nodeinfo_t; enum clustercmd_id { CLUSTERCMDID_PING = 0, CLUSTERCMDID_ACK = 1, CLUSTERCMDID_REG = 2, CLUSTERCMDID_GETMYID = 3, CLUSTERCMDID_SETID = 4, CLUSTERCMDID_HT_EXCH = 5, COUNT_CLUSTERCMDID }; typedef enum clustercmd_id clustercmd_id_t; struct clustercmd_getmyid { char node_name[1]; }; typedef struct clustercmd_getmyid clustercmd_getmyid_t; struct clustercmd_setiddata { uint32_t updatets; char node_name[1]; }; typedef struct clustercmd_setiddata clustercmd_setiddata_t; struct clustercmd_reg { char node_name[1]; }; 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[1]; }; typedef struct clustercmd_ht_exch clustercmd_ht_exch_t; struct clustercmdadler32 { uint32_t hdr; uint32_t dat; }; 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; // 64 uint32_t data_len; // 96 uint32_t ts; // 128 uint32_t serial; // 160 }; typedef struct clustercmdhdr clustercmdhdr_t; struct clustercmd { clustercmdhdr_t h; union data { char p[1]; clustercmd_setiddata_t setid; clustercmd_reg_t reg; clustercmd_ack_t ack; clustercmd_rej_t rej; clustercmd_getmyid_t getmyid; clustercmd_ht_exch_t ht_exch; } 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(options_t *options_p, indexes_t *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.2.1/common.h000066400000000000000000000121651222700035500144470ustar00rootroot00000000000000/* 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 . */ #define _GNU_SOURCE #define _XOPEN_SOURCE 700 #define _LARGEFILE64_SOURCE #define PROGRAM "clsync" #define VERSION_MAJ 0 #define VERSION_MIN 2 #define AUTHOR "Dmitry Yu Okunev 0x8E30679C" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FANOTIFY_SUPPORT #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CAPABILITIES #include // for capset()/capget() for --preserve-file-access #include // for prctl() for --preserve-fil-access #endif #include "configuration.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clsync.h" #include "options.h" #include "indexes.h" #ifndef MIN #define MIN(a,b) ((a)>(b)?(b):(a)) #endif #ifndef MAX #define MAX(a,b) ((a)>(b)?(a):(b)) #endif #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 #define TOSTR(a) # a #define XTOSTR(a) TOSTR(a) #define COLLECTDELAY_INSTANT ((unsigned int)~0) enum paramsource_enum { PS_UNKNOWN = 0, PS_ARGUMENT, PS_CONFIG }; typedef enum paramsource_enum paramsource_t; enum notifyengine_enum { NE_UNDEFINED = 0, #ifdef FANOTIFY_SUPPORT NE_FANOTIFY, #endif NE_INOTIFY }; typedef enum notifyengine_enum notifyenfine_t; #define STATE_STARTING(state_p) (state_p == NULL) enum state_enum { STATE_EXIT = 0, STATE_STARTING, STATE_RUNNING, STATE_REHASH, STATE_TERM, STATE_PTHREAD_GC, STATE_INITSYNC, STATE_UNKNOWN }; typedef enum state_enum state_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; typedef int (*thread_callbackfunct_t)(options_t *options_p, char **argv); struct threadinfo { int thread_num; thread_callbackfunct_t callback; char **argv; pthread_t pthread; int exitcode; int errcode; state_t state; options_t *options_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; enum pthread_mutex_id { PTHREAD_MUTEX_STATE, PTHREAD_MUTEX_MAX }; 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; struct dosync_arg { int evcount; char excf_path[PATH_MAX+1]; char outf_path[PATH_MAX+1]; FILE *outf; options_t *options_p; indexes_t *indexes_p; void *data; int linescount; api_eventinfo_t *api_ei; int api_ei_count; char buf[BUFSIZ+1]; }; 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 entry { size_t size; size_t alloc; void *dat; }; struct pushentry_arg { int allocated; int total; size_t size; struct entry *entry; }; enum initsync { INITSYNC_UNKNOWN = 0, INITSYNC_FULL, INITSYNC_SUBDIR }; typedef enum initsync initsync_t; struct sighandler_arg { options_t *options_p; // indexes_t *indexes_p; pthread_t pthread_parent; int *exitcode_p; sigset_t *sigset_p; }; typedef struct sighandler_arg sighandler_arg_t; clsync-0.2.1/configuration.h000066400000000000000000000036141222700035500160250ustar00rootroot00000000000000 #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) // children count limit #define MAXCHILDREN (1<<8) #define DEFAULT_RULES_PERM RA_ALL #define DEFAULT_NOTIFYENGINE NE_INOTIFY #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 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 0 //(FD_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<<24) /* 16 MiB */ #define CONFIG_PATHS { ".clsync.conf", "/etc/clsync/clsync.conf", NULL } /* "~/.clsync.conf" and "/etc/clsync/clsync.conf" */ #define API_PREFIX "clsyncapi_" clsync-0.2.1/configure.ac000066400000000000000000000054641222700035500153000ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.59]) AC_INIT([clsync],[0.2],[Dmitry Yu Okunev ],,[https://github.com/xaionaro/clsync]) AC_CONFIG_SRCDIR([sync.c]) AM_INIT_AUTOMAKE([1.11 foreign -Wall]) AC_CONFIG_HEADERS([config.h]) 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+=" -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+=" -DHAVE_MHASH"], [AC_MSG_ERROR("Unable to find libmhash")]) ]) ]) dnl --enable-debug AC_ARG_ENABLE(debug, AS_HELP_STRING(--enable-debug, [enable debugging, default: no])) AS_IF([test "x$enable_debug" = "xyes"], [CFLAGS+=" -pipe -Wall -ggdb3" CPPFLAGS+=" -D_DEBUG"]) dnl --paranoid-level 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 --paranoid-level]) ;; esac], [paranoid=1]) AS_IF([test $paranoid -ge 1],[ CPPFLAGS+=" -D_FORTIFY_SOURCE=2 -DPARANOID" CFLAGS+=" -fstack-protector-all -Wall --param ssp-buffer-size=4 -fstack-check" LDFLAGS+=" -Xlinker -zrelro" ]) AS_IF([test $paranoid -eq 2], [CPPFLAGS+=" -DVERYPARANOID"]) # 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+=' -rdynamic'], [AC_MSG_ERROR("Unable to find libdl")]) dnl pthread AC_CHECK_LIB([pthread], [pthread_create], [CPPFLAGS+=" -pthread" LDFLAGS+=" -pthread"], [AC_MSG_ERROR("Pthread support is mandatory")]) PKG_CHECK_MODULES(GLIB, [glib-2.0]) 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 capabilities check AC_ARG_WITH(capabilities, AS_HELP_STRING(--with-capabilities, [enable linux capabilities support, default: disabled])) AS_IF( [test "x$with_capabilities" = "xyes"], [ AC_CHECK_HEADER( [sys/capability.h], [CPPFLAGS+=" -DHAVE_CAPABILITIES"], [AC_MSG_ERROR("Unable to find sys/capability.h")]) ]) LIBS="${GLIB_LIBS} ${LIBS}" AM_CPPFLAGS="${GLIB_CFLAGS}" AC_SUBST(AM_CPPFLAGS) AC_CONFIG_FILES([Makefile]) AC_OUTPUT clsync-0.2.1/debian/000077500000000000000000000000001222700035500142235ustar00rootroot00000000000000clsync-0.2.1/debian/changelog000066400000000000000000000002321222700035500160720ustar00rootroot00000000000000clsync (0.0-1) unstable; urgency=low * Initial release (Closes: #718769 ) -- Artyom A Anikeev Wed, 17 Jul 2013 21:25:45 +0400 clsync-0.2.1/debian/clsync.init000077500000000000000000000044701222700035500164130ustar00rootroot00000000000000#! /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.2.1/debian/compat000066400000000000000000000000021222700035500154210ustar00rootroot000000000000009 clsync-0.2.1/debian/control000066400000000000000000000014511222700035500156270ustar00rootroot00000000000000Source: clsync Section: admin Priority: optional Maintainer: Artyom A Anikeev Build-Depends: debhelper (>= 9), libglib2.0-dev (>= 2.0.0), dh-autoreconf Standards-Version: 3.9.4 Homepage: http://ut.mephi.ru/oss Vcs-Git: git://git.mephi.ru/clsync #Vcs-Browser: http://git.debian.org/?p=collab-maint/clsync.git;a=summary 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. clsync-0.2.1/debian/copyright000066400000000000000000000020321222700035500161530ustar00rootroot00000000000000Format: 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.2.1/debian/default000066400000000000000000000006761222700035500156030ustar00rootroot00000000000000# # 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 -Y -L /dev/shm/clsync -x 23 -W /var/www -S /usr/bin/rsync -D /mnt/backup' # production example: #CLSYNC_ARGS='-M rsyncshell -Y -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.2.1/debian/rules000077500000000000000000000010741222700035500153050ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ --parallel --with autoreconf override_dh_auto_install: dh_auto_install -rm --verbose debian/clsync/usr/share/doc/clsync/LICENSE clsync-0.2.1/debian/source/000077500000000000000000000000001222700035500155235ustar00rootroot00000000000000clsync-0.2.1/debian/source/format000066400000000000000000000000141222700035500167310ustar00rootroot000000000000003.0 (quilt) clsync-0.2.1/debian/watch000066400000000000000000000002131222700035500152500ustar00rootroot00000000000000version=3 opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/clsync-$1.tar.gz/ \ https://github.com/xaionaro/clsync/tags .*/v?(\d\S*)\.tar\.gz clsync-0.2.1/examples/000077500000000000000000000000001222700035500146175ustar00rootroot00000000000000clsync-0.2.1/examples/clsync-start-cluster.sh000077500000000000000000000011241222700035500212610ustar00rootroot00000000000000#!/bin/bash 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,to,listdir} cat > rules < rules < rules < rules < rules < #include // Required header: #include // Optional headers: #include #include #include struct options *options_p = NULL; struct indexes *indexes_p = NULL; char *argv[10] = {NULL}; // Optional function, you can erase it. int clsyncapi_init(struct options *_options_p, struct indexes *_indexes_p) { printf_d("clsyncapi_init(): Hello world!\n"); options_p = _options_p; indexes_p = _indexes_p; if(options_p->destdir == NULL) { printf_e("Error: clsyncapi_init(): dest-dir is not set.\n"); return EINVAL; } if(options_p->flags[RSYNCPREFERINCLUDE]) { printf_e("Error: clsync-synchandler-rsyncso.so cannot be used in conjunction with \"--rsync-prefer-include\" option.\n"); return EINVAL; } if(options_p->flags[PTHREAD]) { printf_e("Error: clsyncapi_init(): this handler is not pthread-safe.\n"); return EINVAL; } argv[0] = "/usr/bin/rsync"; argv[1] = options_p->flags[DEBUG] >= 4 ? "-avvvvvvH" : "-aH"; argv[2] = "--exclude-from"; argv[4] = "--include-from"; argv[6] = "--exclude=*"; argv[7] = options_p->watchdirwslash; argv[8] = options_p->destdirwslash; return 0; } int clsyncapi_rsync(const char *inclistfile, const char *exclistfile) { printf_d("clsyncapi_rsync(): inclistfile == \"%s\"; exclistfile == \"%s\"\n", inclistfile, exclistfile); argv[3] = (char *)exclistfile; argv[5] = (char *)inclistfile; if(options_p->flags[DEBUG] >= 3) { int i=0; while(argv[i] != NULL) { printf_ddd("Debug3: clsyncapi_rsync(): argv[%i] == \"%s\"\n", i, argv[i]); i++; } } // Forking int pid = clsyncapi_fork(options_p); switch(pid) { case -1: printf_e("Error: Cannot fork(): %s (errno: %i).\n", strerror(errno), errno); return errno; case 0: execvp(argv[0], (char *const *)argv); return errno; } int status; if(waitpid(pid, &status, 0) != pid) { printf_e("Error: Cannot waitid(): %s (errno: %i).\n", strerror(errno), errno); return errno; } // Return int exitcode = WEXITSTATUS(status); printf_d("clsyncapi_rsync(): Execution completed with exitcode %i.\n", exitcode); return exitcode; } // Optional function, you can erase it. int clsyncapi_deinit() { printf_d("clsyncapi_deinit(): Goodbye cruel world!\n"); return 0; } clsync-0.2.1/examples/clsync-synchandler-so.c000066400000000000000000000046571222700035500212210ustar00rootroot00000000000000 #include #include // Required header: #include // Optional headers: #include #include #include struct options *options_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 options *_options_p, struct indexes *_indexes_p) { printf_d("clsyncapi_init(): Hello world! API version is %i\n", clsyncapi_getapiversion()); options_p = _options_p; indexes_p = _indexes_p; if(options_p->destdir == NULL) { printf_e("Error: clsyncapi_init(): dest-dir is not set.\n"); return EINVAL; } if(options_p->flags[PTHREAD]) { printf_e("Error: clsyncapi_init(): this handler is not pthread-safe.\n"); 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) { printf_d("clsyncapi_sync(): n == %i\n", n, ei->path); if(n+4 > argv_size) { // "/bin/cp" + "-pf" + n paths + options_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) { printf_d("clsyncapi_sync(): ei[%i].path == \"%s\" (len == %i, type_o == %i, type_n == %i)\n", 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) { printf_d("clsyncapi_sync(): Nothing to sync.\n"); return 0; } argv[argv_i++] = options_p->destdir; argv[argv_i++] = NULL; // Forking int pid = clsyncapi_fork(options_p); switch(pid) { case -1: printf_e("Error: Cannot fork(): %s (errno: %i).\n", strerror(errno), errno); return errno; case 0: chdir(options_p->watchdir); execvp(argv[0], (char *const *)argv); return errno; } int status; if(waitpid(pid, &status, 0) != pid) { printf_e("Error: Cannot waitid(): %s (errno: %i).\n", strerror(errno), errno); return errno; } // Return int exitcode = WEXITSTATUS(status); printf_d("clsyncapi_sync(): Execution completed with exitcode %i.\n", exitcode); return exitcode; } // Optional function, you can erase it. int clsyncapi_deinit() { printf_d("clsyncapi_deinit(): Goodbye cruel world!\n"); if(argv != NULL) free(argv); return 0; } clsync-0.2.1/examples/production/000077500000000000000000000000001222700035500170055ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/000077500000000000000000000000001222700035500175605ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/000077500000000000000000000000001222700035500210535ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/clsync.conf000066400000000000000000000026601222700035500232210ustar00rootroot00000000000000# 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 based HA-cluster for LXC [lxc-brother] background=1 mode=rsyncshell sync-handler=/etc/clsync/synchandler/lxc/brother.sh debug=1 syslog=1 full-initialsync=1 delay-sync=15 delay-collect=15 rules-path=/etc/clsync/rules/lxc lists-dir=/tmp/clsync retries=3 exit-hook=/etc/clsync/hooks/lxc/exit-brother.sh [lxc-backup] background=1 mode=rsyncshell sync-handler=/etc/clsync/synchandler/lxc/backup.sh debug=1 syslog=1 full-initialsync=1 delay-sync=1800 delay-collect=1800 rules-path=/etc/clsync/rules/lxc lists-dir=/tmp/clsync retries=3 exit-hook=/etc/clsync/hooks/lxc/exit-backup.sh clsync-0.2.1/examples/production/etc/clsync/hooks/000077500000000000000000000000001222700035500221765ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/hooks/lxc/000077500000000000000000000000001222700035500227645ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/hooks/lxc/exit-backup.sh000077500000000000000000000001231222700035500255330ustar00rootroot00000000000000#!/bin/bash LABEL="$1" brotherssh rm -f /srv/lxc/"$LABEL"/clsync-backup.status clsync-0.2.1/examples/production/etc/clsync/hooks/lxc/exit-brother.sh000077500000000000000000000001241222700035500257340ustar00rootroot00000000000000#!/bin/bash LABEL="$1" brotherssh rm -f /srv/lxc/"$LABEL"/clsync-brother.status clsync-0.2.1/examples/production/etc/clsync/rules/000077500000000000000000000000001222700035500222055ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/rules/hpc000066400000000000000000000000571222700035500227040ustar00rootroot00000000000000+f^passwd +f^group +f^hosts$ +f^machines$ -*.* clsync-0.2.1/examples/production/etc/clsync/rules/hpc-backup000066400000000000000000000002771222700035500241530ustar00rootroot00000000000000-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.2.1/examples/production/etc/clsync/rules/lxc000066400000000000000000000000661222700035500227200ustar00rootroot00000000000000-f/var/log/.* -f/bin\.000 -d/galera_. -d/var/lib/php5 clsync-0.2.1/examples/production/etc/clsync/synchandler/000077500000000000000000000000001222700035500233655ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/synchandler/hpc/000077500000000000000000000000001222700035500241375ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/synchandler/hpc/handler-backup.c000066400000000000000000000065741222700035500271770ustar00rootroot00000000000000/* 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 #define ARGV_SIZE 32 struct options *options_p; const char *const decrement = "decrement"; size_t decrement_size; int clsyncapi_init(struct options *_options_p, struct indexes *_indexes_p) { if (clsyncapi_getapiversion() != CLSYNC_API_VERSION) { printf_e("handler: API version mistmatch: compiled for %i, but have %i", CLSYNC_API_VERSION, clsyncapi_getapiversion()); return -1; } decrement_size = strlen(decrement); options_p = _options_p; if(!options_p->destdir) { printf_e("handler: dest-dir is not set, aborting\n"); return EINVAL; } if(!options_p->destdir) { printf_e("handler: dest-dir is not set, aborting\n"); return EINVAL; } printf_d("handler: Initialization OK\n"); 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; printf_d("handler: sync started for include file %s, exclude file %s\n", incl_file, excl_file); /* form rsync arguments */ int i = 0; argv[i++] = "/usr/bin/rsync"; if (options_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(options_p->destdir); if (!options_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++] = options_p->watchdirwslash; argv[i++] = options_p->destdirwslash; argv[i++] = NULL; // Forking int pid = clsyncapi_fork(options_p); switch(pid) { case -1: printf_e("handler: Can't fork(): %s\n", strerror(errno)); exitcode = errno; goto cleanup; case 0: execv(argv[0], argv); printf_e("handler: Can't exec(): %s\n", strerror(errno)); exit(errno); } int status; if(waitpid(pid, &status, 0) != pid) { printf_e("handler: Can't waitid(): %s\n", strerror(errno)); exitcode = errno; goto cleanup; } // Return exitcode = WEXITSTATUS(status); cleanup: free(argv[back_idx]); return exitcode; } clsync-0.2.1/examples/production/etc/clsync/synchandler/hpc/handler-pdcp.c000066400000000000000000000044271222700035500266530ustar00rootroot00000000000000/* 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 options *options_p; int clsyncapi_init(struct options *_options_p, struct indexes *_indexes_p) { if (clsyncapi_getapiversion() != CLSYNC_API_VERSION) { printf_e("handler: API version mistmatch: compiled for %i, but have %i", CLSYNC_API_VERSION, clsyncapi_getapiversion()); return -1; } options_p = _options_p; printf_d("handler: Initialization OK\n"); return 0; } int clsyncapi_sync(int n, api_eventinfo_t *ei) { size_t argv_size; char **argv; int exitcode; printf_d("handler: Sync requested for %i objects.\n", 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) { printf_d("handler: Nothing to sync.\n"); exitcode = 0; goto cleanup; } argv[i++] = options_p->watchdir; argv[i++] = NULL; // Forking int pid = clsyncapi_fork(options_p); switch(pid) { case -1: printf_e("handler: Can't fork(): %s\n", strerror(errno)); exitcode = errno; goto cleanup; case 0: if (chdir(options_p->watchdir) == -1) { printf_e("handler: Can't chdir(): %s\n", strerror(errno)); exit(errno); } execv(argv[0], argv); printf_e("handler: Can't exec(): %s\n", strerror(errno)); exit(errno); } int status; if(waitpid(pid, &status, 0) != pid) { printf_e("handler: Can't waitid(): %s\n", strerror(errno)); exitcode = errno; goto cleanup; } // Return exitcode = WEXITSTATUS(status); cleanup: free(argv); return exitcode; // do not die on errors } clsync-0.2.1/examples/production/etc/clsync/synchandler/lxc/000077500000000000000000000000001222700035500241535ustar00rootroot00000000000000clsync-0.2.1/examples/production/etc/clsync/synchandler/lxc/backup.sh000077500000000000000000000020071222700035500257560ustar00rootroot00000000000000#!/bin/bash ACTION="$1" LABEL="$2" ARG0="$3" ARG1="$4" FROM="/srv/lxc/${LABEL}" BACKUPMNT="/mnt/backup" BACKUPDECR="/mnt/backup/decrement/${LABEL}" BACKUPMIRROR="/mnt/backup/mirror/${LABEL}" BACKUPHOST=$(backuphost) function rsynclist() { LISTFILE="$1" EXCLISTFILE="$2" excludefrom='' if [ "$EXCLISTFILE" != "" ]; then excludefrom="--exclude-from=${EXCLISTFILE}" 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 -aH --timeout=3600 --inplace --delete-before --exclude-from="/etc/clsync/synchandler/lxc/rsync.exclude" "$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.2.1/examples/production/etc/clsync/synchandler/lxc/brother.sh000077500000000000000000000020741222700035500261620ustar00rootroot00000000000000#!/bin/bash ACTION="$1" LABEL="$2" ARG0="$3" ARG1="$4" FROM="/srv/lxc/${LABEL}" TO="/mnt/mirror/${LABEL}" BROTHERMNT="/mnt/mirror" BROTHERNAME=$(brothername) CLUSTERNAME=$(clustername) FROM="/srv/lxc/${LABEL}" #TO="/mnt/mirror/${LABEL}" TO="rsync://${CLUSTERNAME}@${BROTHERNAME}/lxc/${LABEL}" 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 --exclude-from="/etc/clsync/synchandler/lxc/rsync.exclude" "$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.2.1/examples/production/etc/clsync/synchandler/lxc/rsync.exclude000066400000000000000000000003651222700035500266700ustar00rootroot00000000000000/sess_* /nanocacmail/*/*.nexus /home/mrtg/*.html /home/mrtg/*.log /home/mrtg/*.old /home/mrtg/*.png /vim/spell/* /var/lib/mysql/* /tmp/* /sys/* /proc/* /run/* /var/tmp/* /var/lock/* /var/run/* /radwtmp /bitrix/cache/* /galera_?/*** /phobos/*** clsync-0.2.1/fileutils.c000066400000000000000000000103571222700035500151530ustar00rootroot00000000000000/* 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 "common.h" #include "output.h" #include "malloc.h" char *fd2fpath_malloc(int fd) { struct stat64 lstat; if(fd <= 0) { printf_e("Error: Invalid file descriptor supplied: fd2fpath_malloc(%i).\n", fd); errno = EINVAL; return NULL; } char *fpath = xmalloc((1<<8) + 2); sprintf(fpath, "/proc/self/fd/%i", fd); if(lstat64(fpath, &lstat)) { printf_e("Error: Cannot lstat(\"%s\", lstat): %s (errno: %i).\n", fpath, strerror(errno), errno); return NULL; } ssize_t fpathlen = lstat.st_size; if(fpathlen > (1<<8)) fpath = xrealloc(fpath, fpathlen+2); printf_ddd("Debug2: Getting file path from symlink \"%s\". Path length is: %i.\n", fpath, fpathlen); if((fpathlen = readlink(fpath, fpath, fpathlen+1)) < 0) { printf_e("Error: Cannot readlink(\"%s\", fpath, bufsize).\n", fpath); return NULL; } printf_ddd("Debug2: The path is: \"%s\"\n", fpath); fpath[fpathlen] = 0; return fpath; } /** * @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) { printf_e("Error: fileutils_copy(\"%s\", \"%s\"): Cannot open file \"%s\" for reading: %s (errno: %i)\n", path_from, path_to, path_from, strerror(errno), errno); return errno; } to = fopen(path_to, "w"); if(to == NULL) { printf_e("Error: fileutils_copy(\"%s\", \"%s\"): Cannot open file \"%s\" for writing: %s (errno: %i)\n", path_from, path_to, path_to, strerror(errno), errno); return errno; } while(!feof(from)) { int err; size_t r, w; r = fread(buf, 1, BUFSIZ, from); if((err=ferror(from))) { printf_e("Error: fileutils_copy(\"%s\", \"%s\"): Cannot read from file \"%s\": %s (errno: %i)\n", path_from, path_to, path_from, strerror(errno), errno); return errno; // CHECK: Is the "errno" should be used in fread() case? } w = fwrite(buf, 1, r, to); if((err=ferror(to))) { printf_e("Error: fileutils_copy(\"%s\", \"%s\"): Cannot write to file \"%s\": %s (errno: %i)\n", path_from, path_to, path_to, strerror(errno), errno); return errno; // CHECK: is the "errno" should be used in fwrite() case? } if(r != w) { printf_e("Error: fileutils_copy(\"%s\", \"%s\"): Got error while writing to file \"%s\" (%u != %u): %s (errno: %i)\n", path_from, path_to, path_to, r, w, strerror(errno), errno); 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) { printf_e("Error: fileutils_calcdirlevel(): path is NULL.\n"); errno=EINVAL; return -1; } if(*path == 0) { printf_e("Error: fileutils_calcdirlevel(): path has zero length.\n"); errno=EINVAL; return -2; } if(*path != '/') { printf_e("Error: fileutils_calcdirlevel(): path \"%s\" is not canonized.\n", path); errno=EINVAL; return -3; } while(*(ptr++)) if(*ptr == '/') dirlevel++; return dirlevel; } clsync-0.2.1/fileutils.h000066400000000000000000000017351222700035500151600ustar00rootroot00000000000000/* 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 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); clsync-0.2.1/gentoo/000077500000000000000000000000001222700035500142745ustar00rootroot00000000000000clsync-0.2.1/gentoo/app-admin/000077500000000000000000000000001222700035500161425ustar00rootroot00000000000000clsync-0.2.1/gentoo/app-admin/clsync/000077500000000000000000000000001222700035500174355ustar00rootroot00000000000000clsync-0.2.1/gentoo/app-admin/clsync/ChangeLog000066400000000000000000000051201222700035500212050ustar00rootroot00000000000000# ChangeLog for app-admin/clsync # Copyright 1999-2013 Gentoo Foundation; Distributed under the GPL v2 # $Header: $ 27 Sep 2013; Andrew Savchenko files/clsync.initd: Remove tread killer hack, because bug #44 was fixed. 21 Sep 2013; Andrew Savchenko files/clsync.initd: Terminate clsync offsprings which are still alive after clsync termination. clsync send signals to its childs, but not to further offsprings (grandchildren and so on), so this offsprings may not die properly in childs doesn't handle this. Thus sophisticated measures are required for cleanup. 20 Sep 2013; Andrew Savchenko clsync-9999.ebuild, files/clsync.confd, files/clsync.initd: Depend mhash on clsync. Inform users about multiplexed init script. Update options names. Add nice, ionice and retry support. 16 Sep 2013; Andrew Savchenko clsync-9999.ebuild, +files/clsync-0.1.confd, +files/clsync-0.1.initd, +files/clsync.conf, files/clsync.confd, files/clsync.initd: Add config file support. For v0.1 use old init.d/conf.d because v0.1 doesn't support config files. 13 Sep 2013; Andrew Savchenko files/clsync.initd: Remove non-standard retry approach, this bug is fixed now. Add syslog support. 09 Sep 2013; Andrew Savchenko clsync-9999.ebuild: Force 0700 permissions or /etc/clsync. License update. Conf.d recommendations update. *clsync-0.1 (07 Sep 2013) 07 Sep 2013; Andrew Savchenko +clsync-0.1.ebuild, clsync-9999.ebuild, +files/clsync.confd, +files/clsync.initd, metadata.xml: Version bump for 0.1 release. Add capabilities support. Install init script and conf file. Keep config directory: rules are supposed to be here. Add rsync hinting information. 26 Aug 2013; Andrew Savchenko clsync-9999.ebuild, metadata.xml: Add mhash and clustering support. 21 Aug 2013; Andrew Savchenko clsync-9999.ebuild: Fix typo if docs path. 21 Aug 2013; Andrew Savchenko clsync-9999.ebuild, metadata.xml: Use new features of clsync build system. Utilize all flags. 21 Aug 2013; Andrew Savchenko -clsync-0.0.ebuild, clsync-9999.ebuild: Base autotools version. Installation and flags control bugs are still to be fixed in clsync itself. 20 Aug 2013; Andrew Savchenko -clsync-0.0.ebuild, clsync-9999.ebuild: Fix html and examples installation. Remove buggy v0.0. 20 Aug 2013; Andrew Savchenko ChangeLog: Initial ebuild. clsync-0.2.1/gentoo/app-admin/clsync/clsync-0.1.ebuild000066400000000000000000000036061222700035500224170ustar00rootroot00000000000000# Copyright 1999-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: $ EAPI=5 if [[ ${PV} == "9999" ]] ; then _GIT=git-2 EGIT_REPO_URI="https://github.com/xaionaro/${PN}.git" SRC_URI="" KEYWORDS="" else SRC_URI="https://github.com/xaionaro/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz" KEYWORDS="~x86 ~amd64" fi inherit autotools $_GIT DESCRIPTION="Live sync tool based on inotify, written in GNU C" HOMEPAGE="http://ut.mephi.ru/oss" LICENSE="GPL-3+" SLOT="0" IUSE="-caps -cluster debug doc +examples extra-hardened hardened +mhash" REQUIRED_USE=" extra-hardened? ( hardened )" RDEPEND=" caps? ( sys-libs/libcap ) mhash? ( app-crypt/mhash ) dev-libs/glib:2 " DEPEND="${RDEPEND} virtual/pkgconfig doc? ( app-doc/doxygen ) " src_prepare() { eautoreconf } src_configure() { local harden_level=0 use hardened && harden_level=1 use extra-hardened && harden_level=2 econf \ --docdir="${EPREFIX}/usr/share/doc/${PF}" \ --enable-paranoid=${harden_level} \ $(use_enable cluster) \ $(use_enable debug) \ $(use_with caps capabilities) \ $(use_with mhash) } src_compile() { emake use doc && emake doc } src_install() { emake DESTDIR="${D}" install use doc && dohtml -r doc/html/* # remove unwanted docs rm "${ED}/usr/share/doc/${PF}"/{LICENSE,TODO} || die use examples || rm -r "${ED}/usr/share/doc/${PF}/examples" || die # filter rules and sync scripts are supposed to be here keepdir "${EPREFIX}/etc/${PN}" newinitd "${FILESDIR}/${P}.initd" "${PN}" newconfd "${FILESDIR}/${P}.confd" "${PN}" } pkg_postinst() { einfo "${PN} is just a convenient way to run synchronization tools on live data," einfo "it doesn't copy data itself, so you need to install software to do actual" einfo "data transfer. Usually net-misc/rsync is a good choise, but ${PN} is" einfo "is flexible enough to use any user tool, see manual page for details." } clsync-0.2.1/gentoo/app-admin/clsync/clsync-9999.ebuild000066400000000000000000000041461222700035500225440ustar00rootroot00000000000000# Copyright 1999-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: $ EAPI=5 if [[ ${PV} == "9999" ]] ; then _GIT=git-2 EGIT_REPO_URI="https://github.com/xaionaro/${PN}.git" SRC_URI="" KEYWORDS="" else SRC_URI="https://github.com/xaionaro/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz" KEYWORDS="~x86 ~amd64" fi inherit autotools $_GIT DESCRIPTION="Live sync tool based on inotify, written in GNU C" HOMEPAGE="http://ut.mephi.ru/oss" LICENSE="GPL-3+" SLOT="0" IUSE="-caps -cluster debug doc +examples extra-hardened hardened mhash" REQUIRED_USE=" extra-hardened? ( hardened ) mhash? ( cluster )" RDEPEND=" caps? ( sys-libs/libcap ) mhash? ( app-crypt/mhash ) dev-libs/glib:2 " DEPEND="${RDEPEND} virtual/pkgconfig doc? ( app-doc/doxygen ) " src_prepare() { eautoreconf } src_configure() { local harden_level=0 use hardened && harden_level=1 use extra-hardened && harden_level=2 econf \ --docdir="${EPREFIX}/usr/share/doc/${PF}" \ --enable-paranoid=${harden_level} \ $(use_enable cluster) \ $(use_enable debug) \ $(use_with caps capabilities) \ $(use_with mhash) } src_compile() { emake use doc && emake doc } src_install() { emake DESTDIR="${D}" install use doc && dohtml -r doc/html/* # remove unwanted docs rm "${ED}/usr/share/doc/${PF}"/{LICENSE,TODO} || die use examples || rm -r "${ED}/usr/share/doc/${PF}/examples" || die newinitd "${FILESDIR}/${PN}.initd" "${PN}" newconfd "${FILESDIR}/${PN}.confd" "${PN}" # filter rules and sync scripts are supposed to be here keepdir "${EPREFIX}/etc/${PN}" insinto "/etc/${PN}" doins "${FILESDIR}/${PN}.conf" } pkg_postinst() { einfo "${PN} is just a convenient way to run synchronization tools on live data," einfo "it doesn't copy data itself, so you need to install software to do actual" einfo "data transfer. Usually net-misc/rsync is a good choise, but ${PN} is" einfo "is flexible enough to use any user tool, see manual page for details." einfo einfo "${PN} init script can now be multiplexed, to use symlink init script to" einfo "othername and use conf.d/othername to configure it." } clsync-0.2.1/gentoo/app-admin/clsync/files/000077500000000000000000000000001222700035500205375ustar00rootroot00000000000000clsync-0.2.1/gentoo/app-admin/clsync/files/clsync-0.1.confd000066400000000000000000000010151222700035500233360ustar00rootroot00000000000000# /etc/conf.d/clsync: config file for /etc/init.d/clsync # See man pages of clsync for valid cmdline options. # Also check examples directory (/usr/share/doc/clsync-*/examples). # # General recommendations: # 1. Put --dir-lists on tmpfs. # 2. Use --uid and --gid to drop privileges whenever possible. # 3. Keeep your clsync rules and sync scripts in /etc/clsync/. #CLSYNC_OPTS="" # Example of direct rsync usage: #CLSYNC_OPTS="--dir-lists /tmp/clsync -RR -p /source/dir #/usr/bin/rsync /etc/clsync/rules /destination/dir" clsync-0.2.1/gentoo/app-admin/clsync/files/clsync-0.1.initd000077500000000000000000000004601222700035500233620ustar00rootroot00000000000000#!/sbin/runscript # Copyright 1999-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: $ command="/usr/bin/clsync" pidfile="/var/run/${SVCNAME}.pid" command_args="--background --syslog --pid-file=${pidfile} ${CLSYNC_OPTS}" retry=5 depend() { use net } clsync-0.2.1/gentoo/app-admin/clsync/files/clsync.conf000066400000000000000000000011121222700035500226740ustar00rootroot00000000000000# clsync system configuration file # # General recommendations: # 1. Put --dir-lists on tmpfs. # 2. Use --uid and --gid to drop privileges whenever possible. # 3. Keeep your clsync rules and sync scripts in /etc/clsync/. [default] # Put your options here, see clsync man pages for a list of valid options. # Also check examples directory (/usr/share/doc/clsync-*/examples). # #watch-dir = /what/dir/to/sync #sync-handler = /etc/clsync/action.sh #rules-dir = /etc/clsync/rules #lists-dir = /tmp/clsync #pthread = 1 #delay-sync = 5 #delay-collect = 5 clsync-0.2.1/gentoo/app-admin/clsync/files/clsync.confd000066400000000000000000000012051222700035500230430ustar00rootroot00000000000000# /etc/conf.d/clsync: config file for /etc/init.d/clsync # config file, default is /etc/clsync/clsync.conf. # to disable set to /dev/null #CLSYNC_CONF="/etc/clsync/clsync.conf" # clsync options, have precedence over config file, # see man clsync and $docdir/examples for details #CLSYNC_OPTS="" # Example of direct rsync usage: #CLSYNC_OPTS="--lists-dir /tmp/clsync --mode rsyncshell #--watch-dir /source/dir --sync-handler /usr/bin/rsync #--rules-file /etc/clsync/rules --distination-dir /destination/dir" # Change clsync nice level (default is unset) #CLSYNC_NICE="0" # Change clsync ionice level (default is unset) #CLSYNC_IONICE="2:7" clsync-0.2.1/gentoo/app-admin/clsync/files/clsync.initd000077500000000000000000000010661222700035500230710ustar00rootroot00000000000000#!/sbin/runscript # Copyright 1999-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: $ [[ -n "${CLSYNC_CONF}" ]] && conffile="--config-file ${CLSYNC_CONF}" [[ -n "${CLSYNC_NICE}" ]] && cmd_nice="--nice ${CLSYNC_NICE}" [[ -n "${CLSYNC_IONICE}" ]] && cmd_ionice="--ionice ${CLSYNC_IONICE}" command="/usr/bin/clsync" pidfile="/var/run/${SVCNAME}.pid" command_args="--background --syslog --pid-file=${pidfile} \ ${conffile} ${CLSYNC_OPTS}" start_stop_daemon_args="${cmd_nice} ${cmd_ionice}" depend() { use net } clsync-0.2.1/gentoo/app-admin/clsync/metadata.xml000066400000000000000000000017421222700035500217430ustar00rootroot00000000000000 bircoph@gmail.com Andrew Savchenko Clsync recursively watches for source directory and executes external program to sync the changes. Clsync is adapted to be used 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 and multicast notifing clsync instances on another nodes to prevent loop syncing. Capabilities support. Under development, may not work properly now. Enable clustering support (allows master-master clsync on multiple hosts). Not fully implemented yet. Enable extra security checks. This may hurt performance. clsync-0.2.1/glibex.c000066400000000000000000000037141222700035500144240ustar00rootroot00000000000000/* 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 "common.h" #include "glibex.h" struct keyvalue_copy_arg { // GHashTable *ht_src; GHashTable *ht_dst; GDupFunc k_dup_funct; GDupFunc v_dup_funct; }; void g_hash_table_foreach_keyvalue_copy(gpointer k, gpointer v, gpointer arg_gp) { // GHashTable *ht_src = ((struct keyvalue_copy_arg *)arg_gp)->ht_src; 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(k), v_dup_funct(v)); return; } 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) { GHashTable *ht_dup = g_hash_table_new_full(hash_funct, key_equal_funct, key_destroy_funct, value_destroy_funct); struct keyvalue_copy_arg arg; // arg.ht_src = ht; arg.ht_dst = ht_dup; arg.k_dup_funct = key_dup_funct; arg.v_dup_funct = value_dup_funct; g_hash_table_foreach(ht, g_hash_table_foreach_keyvalue_copy, &arg); return ht_dup; } clsync-0.2.1/glibex.h000066400000000000000000000021001222700035500144150ustar00rootroot00000000000000/* 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 . */ 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); clsync-0.2.1/indexes.h000066400000000000000000000026011222700035500146100ustar00rootroot00000000000000/* 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 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 }; typedef struct indexes indexes_t; clsync-0.2.1/main.c000066400000000000000000001207361222700035500141020ustar00rootroot00000000000000/* 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 "common.h" #include "output.h" #include "sync.h" #include "malloc.h" #include "cluster.h" #include "fileutils.h" #include "revision.h" static const struct option long_options[] = { {"watch-dir", required_argument, NULL, WATCHDIR}, {"sync-handler", required_argument, NULL, SYNCHANDLER}, {"rules-file", required_argument, NULL, RULESFILE}, {"destination-dir", required_argument, NULL, DESTDIR}, {"mode", required_argument, NULL, MODE}, {"status-file", required_argument, NULL, STATUSFILE}, {"background", optional_argument, NULL, BACKGROUND}, {"config-file", required_argument, NULL, CONFIGFILE}, {"config-block", required_argument, NULL, CONFIGBLOCK}, {"pid-file", required_argument, NULL, PIDFILE}, {"uid", required_argument, NULL, UID}, {"gid", required_argument, NULL, GID}, #ifdef HAVE_CAPABILITIES {"preserve-file-access",optional_argument, NULL, CAP_PRESERVE_FILEACCESS}, #endif {"pthread", optional_argument, NULL, PTHREAD}, {"retries", optional_argument, NULL, RETRIES}, {"syslog", optional_argument, NULL, SYSLOG}, {"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 {"standby-file", required_argument, NULL, STANDBYFILE}, {"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}, {"lists-dir", required_argument, NULL, OUTLISTSDIR}, {"have-recursive-sync", optional_argument, NULL, HAVERECURSIVESYNC}, {"synclist-simplify", optional_argument, NULL, SYNCLISTSIMPLIFY}, {"auto-add-rules-w", optional_argument, NULL, AUTORULESW}, {"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}, {"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}, {"verbose", optional_argument, NULL, VERBOSE}, {"debug", optional_argument, NULL, DEBUG}, {"quiet", optional_argument, NULL, QUIET}, #ifdef FANOTIFY_SUPPORT {"fanotify", optional_argument, NULL, FANOTIFY}, {"inotify", optional_argument, NULL, INOTIFY}, #endif {"label", required_argument, NULL, LABEL}, {"help", optional_argument, NULL, HELP}, {"version", optional_argument, NULL, SHOW_VERSION}, {NULL, 0, NULL, 0} }; static char *const modes[] = { [MODE_UNSET] = "", [MODE_SIMPLE] = "simple", [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_REHASH] = "rehashing", [STATE_TERM] = "terminating", [STATE_PTHREAD_GC] = "pthread gc", [STATE_INITSYNC] = "initsync", NULL }; int syntax() { printf("possible options:\n"); int i=0; while(long_options[i].name != NULL) { if(!(long_options[i].val & OPTION_CONFIGONLY)) printf("\t--%-24s%c%c%s\n", 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" : "")); i++; } exit(EINVAL); } int version() { printf(PROGRAM" v%i.%i"REVISION"\n\t"AUTHOR"\n", VERSION_MAJ, VERSION_MIN); exit(0); } int clsyncapi_getapiversion() { return CLSYNC_API_VERSION; } static inline int parse_parameter(options_t *options_p, uint16_t param_id, char *arg, paramsource_t paramsource) { #ifdef _DEBUG fprintf(stderr, "Force-Debug: parse_parameter(): %i: %i = \"%s\"\n", paramsource, param_id, arg); #endif switch(paramsource) { case PS_ARGUMENT: if(param_id & OPTION_CONFIGONLY) { syntax(); return 0; } options_p->flags_set[param_id] = 1; break; case PS_CONFIG: if(options_p->flags_set[param_id]) return 0; break; default: printf_e("Warning: Unknown parameter #%i source (value \"%s\").\n", param_id, arg!=NULL ? arg : ""); break; } switch(param_id) { case '?': case HELP: syntax(); break; case CONFIGFILE: options_p->config_path = arg; break; case CONFIGBLOCK: options_p->config_block = arg; break; case GID: options_p->gid = (unsigned int)atol(arg); options_p->flags[param_id]++; break; case UID: options_p->uid = (unsigned int)atol(arg); options_p->flags[param_id]++; break; case PIDFILE: options_p->pidfile = arg; break; case RETRIES: options_p->retries = (unsigned int)atol(arg); break; #ifdef CLUSTER_SUPPORT case CLUSTERIFACE: options_p->cluster_iface = arg; break; case CLUSTERMCASTIPADDR: options_p->cluster_mcastipaddr = arg; break; case CLUSTERMCASTIPPORT: options_p->cluster_mcastipport = (uint16_t)atoi(arg); break; case CLUSTERTIMEOUT: options_p->cluster_timeout = (unsigned int)atol(arg); break; case CLUSTERNODENAME: options_p->cluster_nodename = arg; break; case CLUSTERHDLMIN: options_p->cluster_hash_dl_min = (uint16_t)atoi(arg); break; case CLUSTERHDLMAX: options_p->cluster_hash_dl_max = (uint16_t)atoi(arg); break; case CLUSTERSDLMAX: options_p->cluster_scan_dl_max = (uint16_t)atoi(arg); break; #endif case OUTLISTSDIR: options_p->listoutdir = arg; break; case LABEL: options_p->label = arg; break; case STANDBYFILE: if(strlen(arg)) { options_p->standbyfile = arg; options_p->flags[STANDBYFILE] = 1; } else { options_p->standbyfile = NULL; options_p->flags[STANDBYFILE] = 0; } break; case SYNCDELAY: options_p->syncdelay = (unsigned int)atol(arg); break; case DELAY: options_p->_queues[QUEUE_NORMAL].collectdelay = (unsigned int)atol(arg); break; case BFILEDELAY: options_p->_queues[QUEUE_BIGFILE].collectdelay = (unsigned int)atol(arg); break; case BFILETHRESHOLD: options_p->bfilethreshold = (unsigned long)atol(arg); break; #ifdef FANOTIFY_SUPPORT case FANOTIFY: options_p->notifyengine = NE_FANOTIFY; break; #endif case INOTIFY: options_p->notifyengine = NE_INOTIFY; break; case RSYNCINCLIMIT: options_p->rsyncinclimit = (unsigned int)atol(arg); break; case SYNCTIMEOUT: options_p->synctimeout = (unsigned int)atol(arg); break; case EXITHOOK: if(strlen(arg)) { options_p->exithookfile = arg; options_p->flags[EXITHOOK] = 1; } else { options_p->exithookfile = NULL; options_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) options_p->isignoredexitcode[i++] = 0; #ifdef _DEBUG fprintf(stderr, "Force-Debug: parse_parameter(): Reset ignored exitcodes.\n"); #endif } else { options_p->isignoredexitcode[exitcode] = 1; #ifdef _DEBUG fprintf(stderr, "Force-Debug: parse_parameter(): Adding ignored exitcode %u.\n", exitcode); #endif } start = ptr+1; break; } } while(*(ptr++)); break; } case SHOW_VERSION: version(); break; case WATCHDIR: options_p->watchdir = arg; break; case SYNCHANDLER: options_p->handlerfpath = arg; break; case RULESFILE: options_p->rulfpath = arg; break; case DESTDIR: options_p->destdir = arg; break; case STATUSFILE: options_p->statusfile = arg; break; case MODE: { char *value; options_p->flags[MODE] = getsubopt(&arg, modes, &value); if(options_p->flags[MODE] == -1) { fprintf(stderr, "Error: Wrong mode name entered: \"%s\"\n", arg); return EINVAL; } break; } default: if(arg == NULL) options_p->flags[param_id]++; else options_p->flags[param_id] = atoi(arg); #ifdef _DEBUG fprintf(stderr, "Force-Debug: flag %i is set to %i\n", param_id&0xff, options_p->flags[param_id]); #endif break; } return 0; } int arguments_parse(int argc, char *argv[], struct options *options_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 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(options_p, c, optarg, PS_ARGUMENT); if(ret) return ret; } if(optind+1 < argc) syntax(); /* if(optind+1 >= argc) syntax(); options_p->handlerfpath = argv[optind+1]; if(optind+2 < argc) { options_p->rulfpath = argv[optind+2]; if(!strcmp(options_p->rulfpath, "")) options_p->rulfpath = NULL; } if(optind+3 < argc) { options_p->destdir = argv[optind+3]; options_p->destdirlen = strlen(options_p->destdir); } options_p->watchdir = argv[optind]; options_p->watchdirlen = strlen(options_p->watchdir);*/ /* if(optind+0 < argc) { options_p->watchdir = argv[optind]; options_p->watchdirlen = strlen(options_p->watchdir); } else { options_p->watchdir = NULL; options_p->watchdirlen = 0; } if(optind+1 < argc) { options_p->handlerfpath = argv[optind+1]; } else { options_p->handlerfpath = NULL; } if(optind+2 < argc) { options_p->rulfpath = argv[optind+2]; if(!strcmp(options_p->rulfpath, "")) options_p->rulfpath = NULL; } else { options_p->rulfpath = NULL; } if(optind+3 < argc) { options_p->destdir = argv[optind+3]; options_p->destdirlen = strlen(options_p->destdir); } else { options_p->destdir = NULL; options_p->destdirlen = 0; } */ return 0; } char *configs_parse_str[1<<10] = {0}; void gkf_parse(options_t *options_p, GKeyFile *gkf) { const struct option *lo_ptr = long_options; while(lo_ptr->name != NULL) { gchar *value = g_key_file_get_value(gkf, options_p->config_block, lo_ptr->name, NULL); if(value != NULL) { unsigned char val_char = lo_ptr->val&0xff; if(configs_parse_str[val_char]) free(configs_parse_str[val_char]); configs_parse_str[val_char] = value; int ret = parse_parameter(options_p, lo_ptr->val, value, PS_CONFIG); if(ret) exit(ret); } lo_ptr++; } return; } int configs_parse(options_t *options_p) { GKeyFile *gkf; gkf = g_key_file_new(); if(options_p->config_path) { printf_d("Debug: configs_parse(): Trying config-file \"%s\"\n", options_p->config_path); if(!g_key_file_load_from_file(gkf, options_p->config_path, G_KEY_FILE_NONE, NULL)) { printf_e("Error: configs_parse(): Cannot open/parse file \"%s\"\n", options_p->config_path); g_key_file_free(gkf); return -1; } else gkf_parse(options_p, gkf); } 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 = 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] != '/') { 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); printf_d("Debug: configs_parse(): Trying config-file \"%s\"\n", config_path_real); if(!g_key_file_load_from_file(gkf, config_path_real, G_KEY_FILE_NONE, NULL)) { printf_d("Debug: configs_parse(): Cannot open/parse file \"%s\"\n", config_path_real); config_path_p++; continue; } gkf_parse(options_p, gkf); break; } free(config_path_real); } g_key_file_free(gkf); return 0; } int configs_cleanup() { int i=0; while(i < (1<<10)) { if(configs_parse_str[i] != NULL) { free(configs_parse_str[i]); configs_parse_str[i] = NULL; } i++; } return 0; } int rule_complete(rule_t *rule_p, const char *expr) { printf_ddd("Debug3: rule_complete(): <%s>.\n", expr); #ifdef VERYPARANOID if(rule_p->mask == RA_NONE) { printf_e("Error: rule_complete(): Received a rule with rule_p->mask == 0x00. Exit.\n"); return EINVAL; } #endif char buf[BUFSIZ]; int ret = 0; if(rule_p->num >= MAXRULES) { printf_e("Error: Too many rules (%i >= %i).\n", rule_p->num, MAXRULES); return ENOMEM; } if((ret = regcomp(&rule_p->expr, expr, REG_EXTENDED | REG_NOSUB))) { regerror(ret, &rule_p->expr, buf, BUFSIZ); printf_e("Error: Invalid regexp pattern <%s>: %s (regex-errno: %i).\n", expr, buf, ret); return ret; } return ret; } int parse_rules_fromfile(options_t *options_p) { int ret = 0; char *rulfpath = options_p->rulfpath; rule_t *rules = options_p->rules; 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; printf_e("Error: Cannot open \"%s\" for reading: %s (errno: %i).\n", rulfpath, strerror(errno), errno); 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: printf_e("Error: Wrong rule action <%c>.\n", *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( (options_p->flags[MODE] == MODE_RSYNCDIRECT) || (options_p->flags[MODE] == MODE_RSYNCSHELL) || (options_p->flags[MODE] == MODE_RSYNCSO) ) { printf_e("parse_rules_fromfile(): Warning: Used \"w\" rule in \"--rsync\" case." " This may cause unexpected problems.\n"); } rule->objtype = S_IFDIR; rule->mask = RA_WALK; break; default: printf_e("parse_rules_fromfile(): Warning: Cannot parse the rule <%s>\n", &line[-1]); i--; // Canceling new rule continue; } line++; linelen--; // Parsing the rest part of the line printf_d("Debug: parse_rules_fromfile(): Rule #%i <%c> <%c> pattern <%s> (length: %i).\n", rule->num, line[-2], line[-1], line, linelen); if((ret=rule_complete(rule, line))) goto l_parse_rules_fromfile_end; // Post-processing: line--; linelen++; if(*line != 'w') { // processing --auto-add-rules-w if(options_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; // printf_ddd("Debug3: parse_rules_fromfile(): Don't adding w-rule for \"%s\" due to [*d]-rule for \"%s\"\n", // 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; printf_d("Debug: parse_rules_fromfile(): Rule #%i <+> pattern <%s> (length: %i) [auto].\n", rule->num, expr, exprlen); if((ret=rule_complete(rule, expr))) goto l_parse_rules_fromfile_end; g_hash_table_insert(autowrules_ht, strdup(expr), GINT_TO_POINTER(1)); } } while(end != NULL); } } } } } l_parse_rules_fromfile_end: if(size) free(line_buf); fclose(f); printf_ddd("Debug3: parse_rules_fromfile(): Adding tail-rule #%u (effective #%u).\n", -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 printf_ddd("Debug3: parse_rules_fromfile(): Total (p == %p):\n", rules); i=0; do { printf_ddd("\t%i\t%i\t%p/%p\n", i, rules[i].objtype, (void *)(long)rules[i].perm, (void *)(long)rules[i].mask); i++; } while(rules[i].mask != RA_NONE); #endif return ret; } int becomedaemon() { int pid; signal(SIGPIPE, SIG_IGN); switch((pid = fork())) { case -1: printf_e("Error: Cannot fork(): %s (errno: %i).\n", strerror(errno), errno); return(errno); case 0: setsid(); break; default: printf_d("Debug: fork()-ed, pid is %i.\n", pid); exit(0); } return 0; } int main_cleanup(options_t *options_p) { int i=0; while((i < MAXRULES) && (options_p->rules[i].mask != RA_NONE)) regfree(&options_p->rules[i++].expr); printf_ddd("Debug3: main_cleanup(): %i %i %i %i\n", options_p->watchdirsize, options_p->watchdirwslashsize, options_p->destdirsize, options_p->destdirwslashsize); return 0; } int main_rehash(options_t *options_p) { printf_ddd("Debug3: main_rehash()\n"); int ret=0; main_cleanup(options_p); if(options_p->rulfpath != NULL) { ret = parse_rules_fromfile(options_p); if(ret) printf_e("Error: main_rehash(): Got error from parse_rules_fromfile(): %s (errno: %i).\n", strerror(ret), ret); } else { options_p->rules[0].perm = DEFAULT_RULES_PERM; options_p->rules[0].mask = RA_NONE; // Terminator. End of rules. } return ret; } int main_status_update(options_t *options_p, state_t state) { static state_t state_old = STATE_UNKNOWN; if(options_p->statusfile == NULL) return 0; if(state == state_old) { printf_ddd("Debug3: main_status_update: State unchanged: %u == %u\n", state, state_old); return 0; } FILE *f = fopen(options_p->statusfile, "w"); if(f == NULL) { printf_e("Error: main_status_update(): Cannot open file \"%s\" for writing: %s (errno: %u).\n", options_p->statusfile, strerror(errno), errno); return errno; } #ifdef VERYPARANOID if(status_descr[state] == NULL) { printf_e("Error: main_status_update(): status_descr[%u] == NULL.\n", state); return EINVAL; } #endif printf_ddd("Debug3: main_status_update(): Setting status to %i: %s.\n", state, status_descr[state]); state_old=state; int ret = 0; if(fprintf(f, "%s\n", status_descr[state]) <= 0) { // TODO: check output length printf_e("Error: main_status_update(): Cannot write to file \"%s\": %s (errno: %u).\n", options_p->statusfile, strerror(errno), errno); ret = errno; } if(fclose(f)) { printf_e("Error: main_status_update(): Cannot close file \"%s\": %s (errno: %u).\n", options_p->statusfile, strerror(errno), errno); ret = errno; } return ret; } int main(int argc, char *argv[]) { struct options options; #ifdef CLUSTER_SUPPORT struct utsname utsname; #endif memset(&options, 0, sizeof(options)); int ret = 0, nret; options.notifyengine = DEFAULT_NOTIFYENGINE; options.syncdelay = DEFAULT_SYNCDELAY; options._queues[QUEUE_NORMAL].collectdelay = DEFAULT_COLLECTDELAY; options._queues[QUEUE_BIGFILE].collectdelay= DEFAULT_BFILECOLLECTDELAY; options._queues[QUEUE_INSTANT].collectdelay= COLLECTDELAY_INSTANT; options.bfilethreshold = DEFAULT_BFILETHRESHOLD; options.label = DEFAULT_LABEL; options.rsyncinclimit = DEFAULT_RSYNCINCLUDELINESLIMIT; options.synctimeout = DEFAULT_SYNCTIMEOUT; #ifdef CLUSTER_SUPPORT options.cluster_hash_dl_min = DEFAULT_CLUSTERHDLMIN; options.cluster_hash_dl_max = DEFAULT_CLUSTERHDLMAX; options.cluster_scan_dl_max = DEFAULT_CLUSTERSDLMAX; #endif options.config_block = DEFAULT_CONFIG_BLOCK; options.retries = DEFAULT_RETRIES; arguments_parse(argc, argv, &options); out_init(options.flags); nret = configs_parse(&options); if(nret) ret = nret; out_init(options.flags); main_status_update(&options, STATE_STARTING); #ifdef VERYPARANOID if((options.retries != 1) && options.flags[PTHREAD]) { printf_e("Error: \"--retries\" values should be equal to \"1\" for \"--pthread\" mode.\n"); ret = EINVAL; } #endif if(options.flags[STANDBYFILE] && (options.flags[MODE] == MODE_SIMPLE)) { printf_e("Error: Sorry but option \"--standby-file\" cannot be used in mode \"simple\", yet.\n"); ret = EINVAL; } if(options.flags[PTHREAD] && options.flags[ONLYINITSYNC]) { printf_e("Error: Conflicting options: \"--pthread\" and \"--only-initialsync\" cannot be used together.\n"); ret = EINVAL; } if(options.flags[PTHREAD] && options.flags[EXITONNOEVENTS]) { printf_e("Error: Conflicting options: \"--pthread\" and \"--exit-on-no-events\" cannot be used together.\n"); ret = EINVAL; } if(options.flags[SKIPINITSYNC] && options.flags[EXITONNOEVENTS]) { printf_e("Error: Conflicting options: \"--skip-initialsync\" and \"--exit-on-no-events\" cannot be used together.\n"); ret = EINVAL; } if(options.flags[ONLYINITSYNC] && options.flags[EXITONNOEVENTS]) { printf_e("Error: Conflicting options: \"--only-initialsync\" and \"--exit-on-no-events\" cannot be used together.\n"); ret = EINVAL; } if(options.flags[SKIPINITSYNC] && options.flags[ONLYINITSYNC]) { printf_e("Error: Conflicting options: \"--skip-initialsync\" and \"--only-initialsync\" cannot be used together.\n"); ret = EINVAL; } if(options.flags[INITFULL] && options.flags[SKIPINITSYNC]) { printf_e("Error: Conflicting options: \"--full-initialsync\" and \"--skip-initialsync\" cannot be used together.\n"); ret = EINVAL; } if(options.flags[EXCLUDEMOUNTPOINTS]) options.flags[ONEFILESYSTEM]=1; if(options.flags[MODE] == MODE_UNSET) { printf_e("Error: \"--mode\" is not set.\n"); ret = EINVAL; } if(options.watchdir == NULL) { printf_e("Error: \"--watchdir\" is not set.\n"); ret = EINVAL; } if(options.handlerfpath == NULL) { printf_e("Error: \"--sync-handler\" path is not set.\n"); ret = EINVAL; } /* if(options.flags[SYNCHANDLERSO] && options.flags[RSYNC]) { printf_e("Error: Option \"--rsync\" cannot be used in conjunction with \"--synchandler-so-module\".\n"); ret = EINVAL; } */ // if(options.flags[SYNCHANDLERSO] && (options.listoutdir != NULL)) // printf_e("Warning: Option \"--dir-lists\" has no effect conjunction with \"--synchandler-so-module\".\n"); // if(options.flags[SYNCHANDLERSO] && (options.destdir != NULL)) // printf_e("Warning: Destination directory argument has no effect conjunction with \"--synchandler-so-module\".\n"); if((options.flags[MODE] == MODE_RSYNCDIRECT) && (options.destdir == NULL)) { printf_e("Error: Mode \"rsyncdirect\" cannot be used without specifying \"destination directory\".\n"); ret = EINVAL; } #ifdef CLUSTER_SUPPORT if((options.flags[MODE] == MODE_RSYNCDIRECT ) && (options.cluster_iface != NULL)) { printf_e("Error: Mode \"rsyncdirect\" cannot be used in conjunction with \"--cluster-iface\".\n"); ret = EINVAL; } if((options.cluster_iface == NULL) && ((options.cluster_mcastipaddr != NULL) || (options.cluster_nodename != NULL) || (options.cluster_timeout) || (options.cluster_mcastipport))) { printf_e("Error: Options \"--cluster-ip\", \"--cluster-node-name\", \"--cluster_timeout\" and/or \"cluster_ipport\" cannot be used without \"--cluster-iface\".\n"); ret = EINVAL; } if(options.cluster_hash_dl_min > options.cluster_hash_dl_max) { printf_e("Error: \"--cluster-hash-dl-min\" cannot be greater than \"--cluster-hash-dl-max\".\n"); ret = EINVAL; } if(options.cluster_hash_dl_max > options.cluster_scan_dl_max) { printf_e("Error: \"--cluster-hash-dl-max\" cannot be greater than \"--cluster-scan-dl-max\".\n"); ret = EINVAL; } if(!options.cluster_timeout) options.cluster_timeout = DEFAULT_CLUSTERTIMEOUT; if(!options.cluster_mcastipport) options.cluster_mcastipport = DEFAULT_CLUSTERIPPORT; if(!options.cluster_mcastipaddr) options.cluster_mcastipaddr = DEFAULT_CLUSTERIPADDR; if(options.cluster_iface != NULL) { #ifndef _DEBUG printf_e("Error: Cluster subsystem is not implemented, yet. Sorry.\n"); ret = EINVAL; #endif if(options.cluster_nodename == NULL) { if(!uname(&utsname)) options.cluster_nodename = utsname.nodename; printf_d("Debug: cluster node name is: %s\n", options.cluster_nodename); } if(options.cluster_nodename == NULL) { printf_e("Error: Option \"--cluster-iface\" is set, but \"--cluster-node-name\" is not set and cannot get the nodename with uname().\n"); ret = EINVAL; } else { options.cluster_nodename_len = strlen(options.cluster_nodename); } } #endif // CLUSTER_SUPPORT { char *rwatchdir = realpath(options.watchdir, NULL); if(rwatchdir == NULL) { printf_e("Error: main(): Got error while realpath() on \"%s\": %s (errno: %i) [#0].\n", options.watchdir, strerror(errno), errno); ret = errno; } struct stat64 stat64={0}; if(lstat64(options.watchdir, &stat64)) { printf_e("Error: main(): Cannot lstat64() on \"%s\": %s (errno: %i)\n", options.watchdir, strerror(errno), errno); if(!ret) ret = errno; } else { if(options.flags[EXCLUDEMOUNTPOINTS]) options.st_dev = stat64.st_dev; if((stat64.st_mode & S_IFMT) == S_IFLNK) { // The proplems may be due to FTS_PHYSICAL option of ftp_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 printf_e("Error: Watch dir cannot be symlink, but \"%s\" is a symlink.\n", options.watchdir); ret = EINVAL; #else char *watchdir_resolved_part = alloca(PATH_MAX+1); ssize_t r = readlink(options.watchdir, watchdir_resolved_part, PATH_MAX+1); if(r>=PATH_MAX) { // TODO: check if it's possible printf_e("Error: Too long file path resolved from symbolic link \"%s\"\n", options.watchdir); ret = EINVAL; } else if(r<0) { printf_e("Error: Cannot resolve symbolic link \"%s\": readlink() error: %s (errno: %i)\n", options.watchdir, strerror(errno), errno); ret = EINVAL; } else { char *watchdir_resolved; #ifdef VERYPARANOID if(options.watchdirsize) if(options.watchdir != NULL) free(options.watchdir); #endif size_t watchdir_resolved_part_len = strlen(watchdir_resolved_part); options.watchdirsize = watchdir_resolved_part_len+1; // Not true for case of relative symlink if(*watchdir_resolved_part == '/') { // Absolute symlink watchdir_resolved = malloc(options.watchdirsize); memcpy(watchdir_resolved, watchdir_resolved_part, options.watchdirsize); } else { // Relative symlink :( char *rslash = strrchr(options.watchdir, '/'); char *watchdir_resolved_rel = alloca(PATH_MAX+1); size_t watchdir_resolved_rel_len = rslash-options.watchdir + 1; memcpy(watchdir_resolved_rel, options.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); } printf_d("Debug: Symlink resolved: watchdir \"%s\" -> \"%s\"\n", options.watchdir, watchdir_resolved); options.watchdir = watchdir_resolved; } #endif } } if(!ret) { options.watchdir = rwatchdir; options.watchdirlen = strlen(options.watchdir); options.watchdirsize = options.watchdirlen; #ifdef VERYPARANOID if(options.watchdirlen == 1) { printf_e("Very-Paranoid: --watch-dir is supposed to be not \"/\".\n"); ret = EINVAL; } #endif } if(!ret) { if(options.watchdirlen == 1) { options.watchdirwslash = options.watchdir; options.watchdirwslashsize = 0; options.watchdir_dirlevel = 0; } else { size_t size = options.watchdirlen + 2; char *newwatchdir = xmalloc(size); memcpy( newwatchdir, options.watchdir, options.watchdirlen); options.watchdirwslash = newwatchdir; options.watchdirwslashsize = size; memcpy(&options.watchdirwslash[options.watchdirlen], "/", 2); options.watchdir_dirlevel = fileutils_calcdirlevel(options.watchdirwslash); } } } if(options.destdir != NULL) { char *rdestdir = realpath(options.destdir, NULL); if(rdestdir == NULL) { printf_e("Error: main(): Got error while realpath() on \"%s\": %s (errno: %i) [#1].\n", options.destdir, strerror(errno), errno); ret = errno; } if(!ret) { options.destdir = rdestdir; options.destdirlen = strlen(options.destdir); options.destdirsize = options.destdirlen; if(options.destdirlen == 1) { printf_e("Error: destdir is supposed to be not \"/\".\n"); ret = EINVAL; } } if(!ret) { size_t size = options.destdirlen + 2; char *newdestdir = xmalloc(size); memcpy( newdestdir, options.destdir, options.destdirlen); options.destdirwslash = newdestdir; options.destdirwslashsize = size; memcpy(&options.destdirwslash[options.destdirlen], "/", 2); } } printf_d("Debug: %s [%s] (%p) -> %s [%s]\n", options.watchdir, options.watchdirwslash, options.watchdirwslash, options.destdir?options.destdir:"", options.destdirwslash?options.destdirwslash:""); if( ( (options.flags[MODE]==MODE_RSYNCDIRECT) || (options.flags[MODE]==MODE_RSYNCSHELL) || (options.flags[MODE]==MODE_RSYNCSO) ) && (options.listoutdir == NULL) ) { printf_e("Error: Modes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" cannot be used without \"--lists-dir\".\n"); ret = EINVAL; } if( options.flags[RSYNCPREFERINCLUDE] && !( options.flags[MODE] == MODE_RSYNCDIRECT || options.flags[MODE] == MODE_RSYNCSHELL || options.flags[MODE] == MODE_RSYNCSO ) ) printf_e("Warning: Option \"--rsyncpreferinclude\" is useless if mode is not \"rsyncdirect\", \"rsyncshell\" or \"rsyncso\".\n"); if( ( options.flags[MODE] == MODE_RSYNCDIRECT || options.flags[MODE] == MODE_RSYNCSHELL || options.flags[MODE] == MODE_RSYNCSO ) && options.flags[AUTORULESW] ) printf_e("Warning: Option \"--auto-add-rules-w\" in modes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" may cause unexpected problems.\n"); if(options.flags[DEBUG]) debug_print_flags(); if(options.listoutdir != NULL) { struct stat st={0}; errno = 0; if(stat(options.listoutdir, &st)) { if(errno == ENOENT) { printf_e("Warning: Directory \"%s\" doesn't exist. Creating it.\n", options.listoutdir); errno = 0; if(mkdir(options.listoutdir, S_IRWXU)) { printf_e("Error: main(): Cannot create directory \"%s\": %s (errno: %i).\n", options.listoutdir, strerror(errno), errno); ret = errno; } } else { printf_e("Error: main(): Got error while stat() on \"%s\": %s (errno: %i).\n", options.listoutdir, strerror(errno), errno); ret = errno; } } if(!errno) if(st.st_mode & (S_IRWXG|S_IRWXO)) { #ifdef PARANOID printf_e("Error: Insecure: Others have access to directory \"%s\". Exit.\n", options.listoutdir); ret = EACCES; #else printf_e("Warning: Insecure: Others have access to directory \"%s\".\n", options.listoutdir); #endif } } /* if(options.flags[HAVERECURSIVESYNC] && (options.listoutdir == NULL)) { printf_e("Error: main(): Option \"--dir-lists\" should be set to use option \"--have-recursive-sync\".\n"); ret = EINVAL; } */ if( options.flags[HAVERECURSIVESYNC] && ( options.flags[MODE] == MODE_RSYNCDIRECT || options.flags[MODE] == MODE_RSYNCSHELL || options.flags[MODE] == MODE_RSYNCSO ) ) { printf_e("Error: main(): Option \"--have-recursive-sync\" with nodes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" are incompatable.\n"); ret = EINVAL; } if(options.flags[SYNCLISTSIMPLIFY] && (options.listoutdir == NULL)) { printf_e("Error: main(): Option \"--dir-lists\" should be set to use option \"--synclist-simplify\".\n"); ret = EINVAL; } if( options.flags[SYNCLISTSIMPLIFY] && ( options.flags[MODE] == MODE_RSYNCDIRECT || options.flags[MODE] == MODE_RSYNCSHELL || options.flags[MODE] == MODE_RSYNCSO ) ) { printf_e("Error: main(): Option \"--synclist-simplify\" with nodes \"rsyncdirect\" and \"rsyncshell\" are incompatable.\n"); ret = EINVAL; } #ifdef FANOTIFY_SUPPORT if(options.notifyengine != NE_INOTIFY) { printf_e("Warning: fanotify is not fully supported, yet!\n"); } #endif if(options.flags[EXITHOOK]) { #ifdef VERYPARANOID if(options.exithookfile == NULL) { printf_e("Error: main(): options.exithookfile == NULL\n"); ret = EINVAL; } else #endif { if(access(options.exithookfile, X_OK) == -1) { printf_e("Error: \"%s\" is not executable: %s (errno: %i).\n", options.exithookfile, strerror(errno), errno); if(!ret) ret = errno; } } } if(access(options.handlerfpath, X_OK) == -1) { printf_e("Error: \"%s\" is not executable: %s (errno: %i).\n", options.handlerfpath, strerror(errno), errno); if(!ret) ret = errno; } nret=main_rehash(&options); if(nret) ret = nret; if(options.flags[BACKGROUND]) { nret = becomedaemon(); if(nret) ret = nret; } #ifdef HAVE_CAPABILITIES if(options.flags[CAP_PRESERVE_FILEACCESS]) { // Doesn't work, yet :( // // Error: Cannot inotify_add_watch() on "/home/xaionaro/clsync/examples/testdir/from": Permission denied (errno: 13). printf_d("Debug: main(): Preserving access to files with using linux capabilites\n"); 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) { printf_e("Error: main() cannot get capabilites with capget(): %s (errno: %i)\n", strerror(errno), errno); ret = errno; goto preserve_fileaccess_end; } // From "man 7 capabilities": // CAP_DAC_OVERRIDE - Bypass file read, write, and execute permission checks. // CAP_DAC_READ_SEARCH - Bypass file read permission checks and directory read and execute permission checks. cap_dat.effective = (CAP_TO_MASK(CAP_DAC_OVERRIDE)|CAP_TO_MASK(CAP_DAC_READ_SEARCH)|CAP_TO_MASK(CAP_FOWNER)|CAP_TO_MASK(CAP_SYS_ADMIN)|CAP_TO_MASK(CAP_SETUID)); cap_dat.permitted = (CAP_TO_MASK(CAP_DAC_OVERRIDE)|CAP_TO_MASK(CAP_DAC_READ_SEARCH)|CAP_TO_MASK(CAP_FOWNER)|CAP_TO_MASK(CAP_SYS_ADMIN)|CAP_TO_MASK(CAP_SETUID)); cap_dat.inheritable = 0; printf_ddd("Debug3: main(): cap.eff == %p; cap.prm == %p.\n", (void *)(long)cap_dat.effective, (void *)(long)cap_dat.permitted); if(capset(&cap_hdr, &cap_dat) < 0) { printf_e("Error: main(): Cannot set capabilities with capset(): %s (errno: %i).\n", strerror(errno), errno); ret = errno; goto preserve_fileaccess_end; } // Tell kernel not clear capabilities when dropping root if(prctl(PR_SET_KEEPCAPS, 1) < 0) { printf_e("Error: main(): Cannot prctl(PR_SET_KEEPCAPS, 1) to preserve capabilities: %s (errno: %i)\n", strerror(errno), errno); ret = errno; goto preserve_fileaccess_end; } } preserve_fileaccess_end: #endif if(options.flags[UID]) { if(setuid(options.uid)) { printf_e("Error: main(): Cannot setuid(%u): %s (errno: %i)\n", options.uid, strerror(errno), errno); ret = errno; } } if(options.flags[GID]) { if(setuid(options.gid)) { printf_e("Error: main(): Cannot setgid(%u): %s (errno: %i)\n", options.gid, strerror(errno), errno); ret = errno; } } if(options.pidfile != NULL) { pid_t pid = getpid(); FILE *pidfile = fopen(options.pidfile, "w"); if(pidfile == NULL) { printf_e("Error: main(): Cannot open file \"%s\" to write a pid there: %s (errno: %i)\n", options.pidfile, strerror(errno), errno); ret = errno; } else { if(fprintf(pidfile, "%u\n", pid) < 0) { printf_e("Error: main(): Cannot write pid into file \"%s\": %s (errno: %i)\n", options.pidfile, strerror(errno), errno); ret = errno; } fclose(pidfile); } } printf_ddd("Debug3: main(): Current errno is %i.\n", ret); if(ret == 0) { ret = sync_run(&options); } if(options.pidfile != NULL) { if(unlink(options.pidfile)) { printf_e("Error: main(): Cannot unlink pidfile \"%s\": %s (errno: %i)\n", options.pidfile, strerror(errno), errno); ret = errno; } } if(options.statusfile != NULL) { if(unlink(options.statusfile)) { printf_e("Error: main(): Cannot unlink status file \"%s\": %s (errno: %i)\n", options.statusfile, strerror(errno), errno); ret = errno; } } main_cleanup(&options); if(options.watchdirsize) free(options.watchdir); if(options.watchdirwslashsize) free(options.watchdirwslash); if(options.destdirsize) free(options.destdir); if(options.destdirwslashsize) free(options.destdirwslash); configs_cleanup(); out_flush(); printf_d("Debug: finished, exitcode: %i: %s.\n", ret, strerror(ret)); out_flush(); out_deinit(); return ret; } clsync-0.2.1/main.h000066400000000000000000000016261222700035500141030ustar00rootroot00000000000000/* 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 main_rehash(options_t *options_p); extern int main_status_update(options_t *options_p, state_t state); clsync-0.2.1/malloc.c000066400000000000000000000034151222700035500144170ustar00rootroot00000000000000/* 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 "common.h" #include "malloc.h" #include "output.h" void *xmalloc(size_t size) { #ifdef PARANOID size++; // Just in case #endif void *ret = malloc(size); if(ret == NULL) { printf_e("xmalloc(%i): Cannot allocate memory (#%i: %s).\n", size, errno, strerror(errno)); exit(errno); } #ifdef PARANOID memset(ret, 0, size); #endif return ret; } void *xcalloc(size_t nmemb, size_t size) { #ifdef PARANOID nmemb++; // Just in case size++; // Just in case #endif void *ret = calloc(nmemb, size); if(ret == NULL) { printf_e("xcalloc(%i): Cannot allocate memory (#%i: %s).\n", size, errno, strerror(errno)); exit(errno); } // memset(ret, 0, nmemb*size); // Just in case return ret; } void *xrealloc(void *oldptr, size_t size) { #ifdef PARANOID size++; // Just in case #endif void *ret = realloc(oldptr, size); if(ret == NULL) { printf_e("xrealloc(%p, %i): Cannot reallocate memory (#%i: %s).\n", oldptr, size, errno, strerror(errno)); exit(errno); } return ret; } clsync-0.2.1/malloc.h000066400000000000000000000016261222700035500144260ustar00rootroot00000000000000/* 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 . */ void *xmalloc(size_t size); void *xcalloc(size_t nmemb, size_t size); void *xrealloc(void *oldptr, size_t size); clsync-0.2.1/man/000077500000000000000000000000001222700035500135545ustar00rootroot00000000000000clsync-0.2.1/man/man1/000077500000000000000000000000001222700035500144105ustar00rootroot00000000000000clsync-0.2.1/man/man1/clsync.1000066400000000000000000000614561222700035500160010ustar00rootroot00000000000000.\" 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 [ ... ] .SH DESCRIPTION .B clsync executes .I sync\-handler with appropriate arguments on FS events in directory .I watch\-dir using the .BR inotify (7) Linux kernel subsystem. Extended regex\-rules to filter what files and directories to sync may be placed in .I rules\-file .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 .B \-W, \-\-watch\-dir .I watch\-dir .RS 8 Root directory to be monitored by .BR clsync . Required. .PP .RE .B \-S, \-\-sync\-handler .I sync\-handler .RS 8 Path to .I sync\-handler to be used for syncing by .BR clsync . (see .IR \-\-mode ) Required. .PP .RE .B \-R, \-\-rules\-file .I rules\-file .RS 8 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 8 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 8 Sets syncing mode. Possible values: .RS simple \- calls .I sync\-handler for every event .br shell \- calls .I sync\-handler for every sync .br rsyncdirect \- calls rsync by path .I sync\-handler directly (inflexible and unreliable, should be used only as a proof of concept) .br rsyncshell \- calls .I sync\-handler that supposed to run rsync for every sync (recommended mode) .br rsyncso \- loads shared object by path .I sync\-handler with .BR dlopen (3) and calls function .B clsyncapi_rsync function for every sync .br so \- loads shared object by path .I sync\-handler with .BR dlopen (3) and calls function .B clsyncapi_sync function for every sync .RE See .B SYNC HANDLER MODES .PP Required. .RE .B \-b, \-\-background .RS 8 Daemonize, forcing clsync to fork() on start. Is not set by default. .PP .RE .B \-H, \-\-config\-file .I config\-file\-path .RS 8 Use configuration from file .IR config\-file\-path . (see .BR "CONFIGURATION FILE" ) Is not set by default. .PP .RE .B \-K, \-\-config\-block .I config\-block\-name .RS 8 Use configuration block with name .IR config\-block\-name . (see .BR "CONFIGURATION FILE" ) Default value is "default". .PP .RE .B \-z, \-\-pid\-file .I path\-to\-pidfile .RS 8 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 8 Write status description into file with path .IR status\-file\-path . Possible statuses: .RS 8 starting \- initializing subsystems and marking file tree with FS monitor subsystem .br initsync \- processing initial syncing .br running \- waiting for events or syncing .br rehashing \- reloading configuration files .br pthread gc \- running threads' garbage collector .br terminating \- received signal to die, preparing to die .br exiting \- cleaning up [for .BR valgrind (1)] .RE Is not set by default. .PP .RE .B \-u, \-\-uid .I uid .RS 8 Drop user privileges to uid .I uid with .BR setuid (2) Is not set by default. .PP .RE .B \-g, \-\-gid .I gid .RS 8 Drop group privileges to gid .I gid with .BR setgid (2) Is not set by default. .PP .RE .B \-r, \-\-retries .I "number-of-tries" .RS 8 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. Default value is "1". .RE .B \-p, \-\-pthread .RS 8 Use .BR pthreads (7) to parallelize syncing processes. Every sync will be done asynchronous without blocking. If you're running .B clsync with option .B \-\-pthread 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 ) Is not set by default. Don't use this option if not sure. .RE .B \-Y, \-\-syslog .RS 8 Use syslog instead of stderr for logging. Is not set by default. .RE .B \-\-one\-file\-system .RS 8 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 8 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 .PP .B \-c, \-\-cluster\-iface .I interface\-ip .RS 8 .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 8 .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. Default value is "227.108.115.121". [(128+"c")."l"."s"."y"] .RE .PP .B \-P, \-\-cluster\-port .I multicast\-port .RS 8 .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. Default value is "40079". [("n" << 8) + "c"] .RE .PP .B \-W, \-\-cluster\-timeout .I cluster\-timeout .RS 8 .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. Default value is "1000". [1 second] .RE .PP .B \-n, \-\-cluster\-node\-name .I cluster\-node\-name .RS 8 .B Not implemented yet. Sets the name of current node in the cluster. It will be used in action scripts of another nodes (see .BR "SYNC HANDLER MODES" ). Default value is $(uname \-n). .RE .PP .B \-o, \-\-cluster\-hash\-dl\-min .I hash\-dirlevel\-min .RS 8 Sets minimal directory level for ctime hashing (see .BR CLUSTERING ). Default value is "1". .RE .PP .B \-O, \-\-cluster\-hash\-dl\-max .I hash\-dirlevel\-max .RS 8 .B Not implemented yet. Sets maximal directory level for ctime hashing (see .BR CLUSTERING ). Default value is "16". .RE .PP .B \-O, \-\-cluster\-scan\-dl\-max .I scan\-dirlevel\-max .RS 8 .B Not implemented yet. Sets maximal directory level for ctime scanning (see .BR CLUSTERING ). Default value is "32". .RE .PP .B \-\-standby\-file .I standby\-file\-path .RS 8 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 \-k, \-\-timeout\-sync .I sync-timeout .RS 8 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. Default value is "86400" ["24 hours"]. .RE .PP .B \-w, \-\-delay\-sync .I additional\-delay .RS 8 Sets the minimal delay (in seconds) between syncs. Default value is "30". .RE .PP .B \-t, \-\-delay\-collect .I ordinary\-delay .RS 8 Sets the delay (in seconds) to collect events about ordinary files and directories. Default value is "30". .RE .PP .B \-T, \-\-delay\-collect\-bigfile .I bigfiles\-delay .RS 8 Sets the delay (in seconds) to collect events about "big files" (see .IR \-\-threshold\-bigfile ). Default value is "1800". .RE .PP .B \-B, \-\-threshold\-bigfile .I filesize\-threshold .RS 8 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. Default value is "134217728" ["128 MiB"]. .RE .PP .B \-L, \-\-lists\-dir .I tmpdir\-path .RS 8 Sets directory path to output temporary events\-lists files. If this option is enabled, .B clsync will execute .I sync\-handler once for each aggregated event list, passing the path to a file containing this list (actions "synclist" and "rsynclist"). Otherwise, .B clsync will execute .I sync\-handler for every file in the aggregated event list (action "sync"). Cannot be used in mode "so". See .BR "SYNC HANDLER MODES" . Is not set by default. .RE .PP .B \-\-have\-recursive\-sync .RS 8 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 8 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 8 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 8 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. Default value is "20000". .RE .PP .B \-\-rsync\-prefer\-include .RS 8 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 8 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 8 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 \-F, \-\-full\-initialsync .RS 8 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 8 Exit after initial syncing on clsync start. Is not set by default. .RE .PP .B \-\-exit\-on\-no\-events .RS 8 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 8 Skip initial syncing on clsync start. Is not set by default. .RE .PP .B \-\-exit\-hook .I path\-of\-exit\-hook\-program .RS 8 Sets path of program to be executed on clsync exit. If this parameter is set, clsync will exec on exit: .RS .I path\-of\-exit\-hook\-program label .RE The execution will be skipped if syncing process wasn't started. Is not set by default. .RE .PP .B \-v, \-\-verbose .RS 8 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 8 Increases debugging output. This may be supplied multiple times for more debugging information, up to a maximum of three "d" flags (more will do nothing), for example "\-d \-d \-d" or "\-d3" (equivalent cases) Is not set by default. .RE .PP .B \-q, \-\-quiet .RS 8 Suppresses error messages. Is not set by default. .RE .PP .B \-f, \-\-fanotify .RS 8 .B Don't use this option! Switches monitor subsystem to "fanotify" [it's described for future\-compatibility]. Is not set by default. .RE .PP .B \-i, \-\-inotify .RS 8 Switches monitor subsystem to "inotify". Is set by default. .RE .PP .B \-l, \-\-label .I label .RS 8 Sets a label for this instance of clsync. The .I label will be passed to .I sync\-handler every execution. Default value is "nolabel". .RE .PP .B \-h, \-\-help .RS 8 Outputs options list and exits with exitcode "0". Is not set by default. .RE .PP .B \-V, \-\-version .RS 8 Outputs clsync version and exits with exitcode "0". Is not set by default. .RE .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 six ways. Which way will be used depends on specified mode (see .IR \-\-mode ) case .B simple .RS Executes for every syncing file/dir: .br .I sync\-handler sync .I label evmask path [nodes] In this case, .I sync\-handler is supposed to non\-recursively sync file or directory by .IR path . With .I evmask it's passed bitmask of events with the file or directory (see "/usr/include/linux/inotify.h"). Not recommended. Not well tested. .RE case .B shell .RS Executes for every sync (if .B recursivesync is not used instead): .br .I sync\-handler synclist .I label listpath [nodes] Executes for initial syncs if option .I \-\-have\-recursive\-sync is set: .br .I sync\-handler recursivesync .I label dirpath [nodes] In this case, .I sync\-handler is supposed to non\-recursively sync files and directories from list in a file by path .I listpath (see below). With .I evmask it's passed bitmask of events with the file or directory (see "/usr/include/linux/inotify.h"). Also .I sync\-handler is supposed to recursively sync data from directory by path .I dirpath with manual excluding extra files. Not recommended. Not well tested. .RE case .B rsyncdirect .RS Executes for every sync: .br .I sync\-handler \-\-inplace \-aH \-\-delete\-before [\-\-exclude\-from .I rsync\-exclude\-listpath ] \-\-include\-from .I rsync\-listpath \-\-exclude '*' .I watch-dir/ dest-dir/ In this case, .I sync\-handler is supposed to be a path to .B rsync binary. Error code "24" from .I sync\-handler will be ignored in this case. This case is supposed to be used only as a proof of concept. .RE case .B rsyncshell .RS Executes for every sync: .br .I sync\-handler rsynclist .I label rsync\-listpath [nodes] [rsync\-exclude\-listpath] In this case, .I sync\-handler is supposed to run "rsync" application with parameters: \-aH \-\-delete\-before \-\-include\-from .I rsync\-listpath \-\-exclude '*' if option .I \-\-rsync\-prefer\-include is enabled. And with parameters: \-aH \-\-delete\-before \-\-exclude\-from .I rsync\-exclude\-listpath \-\-include\-from .I rsync\-listpath \-\-exclude '*' if option .I \-\-rsync\-prefer\-include is disabled. 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(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-rsyncso.c". Recommended case. IMHO, this way is the best. .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 About the .I label see .IR \-\-label . .br .I nodes is comma-separated list of cluster nodes names where to sync to (see .IR \-\-cluster-node-name ) The listfile by path .I listpath contains lines separated by NL (without CR) of next format: .RS sync .I label evmask path .RS if option .I \-\-synclist\-simplify is not set .RE .I path .RS if option .I \-\-synclist\-simplify is set .RE Every lines is supposed to be proceed by external syncer to sync file or directory by path .IR path . With .I evmask it's passed bitmask of events with the file or directory (see "/usr/include/linux/inotify.h"). .RE .SH RULES Filter riles 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 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 \- to reread filter rules 10 \- runs threads' GC function 12 \- runs full resync 16 \- interrupts sleep()/select() and wait() [for debugging and internal uses] .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 \-\-pthread and rsync with .BR \-\-backup . See 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 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 accessable 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 syslog=1 .br pid-file=/var/run/clsync.pid .br .br [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's 2 blocks are set - "default" and "test". 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 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" with few seconds delay. .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) clsync-0.2.1/options.h000066400000000000000000000110571222700035500146510ustar00rootroot00000000000000/* 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 #define OPTION_LONGOPTONLY (1<<9) #define OPTION_CONFIGONLY (1<<8) enum flags_enum { WATCHDIR = 'W', SYNCHANDLER = 'S', RULESFILE = 'R', DESTDIR = 'D', HELP = 'h', CONFIGFILE = 'H', CONFIGBLOCK = 'K', BACKGROUND = 'b', UID = 'u', GID = 'g', CAP_PRESERVE_FILEACCESS = 'C', PTHREAD = 'p', RETRIES = 'r', SYSLOG = 'Y', EXCLUDEMOUNTPOINTS= 'X', PIDFILE = 'z', #ifdef CLUSTER_SUPPORT CLUSTERIFACE = 'c', CLUSTERMCASTIPADDR='m', CLUSTERMCASTIPPORT='P', CLUSTERTIMEOUT = 'G', CLUSTERNODENAME = 'n', CLUSTERHDLMIN = 'o', CLUSTERHDLMAX = 'O', CLUSTERSDLMAX = 's', #endif 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', #ifdef FANOTIFY_SUPPORT FANOTIFY = 'f', #endif INOTIFY = 'i', 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, }; typedef enum flags_enum flags_t; enum mode_id { MODE_UNSET = 0, MODE_SIMPLE, 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_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 = 0xff }; typedef enum ruleaction_enum ruleaction_t; // signals (man 7 signal) enum sigusr_enum { SIGUSR_PTHREAD_GC = 10, SIGUSR_INITSYNC = 12, SIGUSR_BLOPINT = 16 }; 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 options { uid_t uid; gid_t gid; pid_t child_pid[MAXCHILDREN]; // Used only for non-pthread mode int children; // Used only for non-pthread mode rule_t rules[MAXRULES]; dev_t st_dev; int flags[1<<10]; int flags_set[1<<10]; char *config_path; char *config_block; char *label; char *watchdir; char *pidfile; char *standbyfile; char *exithookfile; char *destdir; char *watchdirwslash; char *destdirwslash; char *statusfile; #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; char *listoutdir; int notifyengine; 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)]; }; typedef struct options options_t; clsync-0.2.1/output.c000066400000000000000000000104541222700035500145110ustar00rootroot00000000000000/* 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 #include "common.h" #include "output.h" static int *flags; printf_funct _printf_ddd=NULL; printf_funct _printf_dd=NULL; printf_funct _printf_d=NULL; printf_funct _printf_v=NULL; printf_funct printf_e=NULL; write_funct _write_ddd=NULL; write_funct _write_dd=NULL; write_funct _write_d=NULL; write_funct _write_v=NULL; write_funct write_e=NULL; int out_init(int *flags_init) { flags = flags_init; // static char buf[OUTPUT_BUFSIZE]; if(!flags[QUIET]) { if(flags[SYSLOG]) { openlog(PROGRAM, LOG_PID, LOG_DAEMON); printf_e = printf_syslog_err; write_e = write_syslog_err; if(flags[SYSLOG]>0) { _printf_d = printf_syslog_debug; _write_d = write_syslog_debug; } if(flags[SYSLOG]>1) { _printf_dd = printf_syslog_debug; _write_dd = write_syslog_debug; } if(flags[SYSLOG]>2) { _printf_ddd = printf_syslog_debug; _write_ddd = write_syslog_debug; } if(flags[VERBOSE]) { _printf_v = printf_syslog_info; _write_v = write_syslog_info; } } else { printf_e = printf_stderr; write_e = write_stderr; if(flags[DEBUG]>0) { _printf_d = printf_e; _write_d = write_e; } if(flags[DEBUG]>1) { _printf_dd = printf_e; _write_dd = write_e; } if(flags[DEBUG]>2) { _printf_ddd = printf_e; _write_ddd = write_e; } if(flags[VERBOSE]) { _printf_v = printf_e; _write_v = write_e; } } } // setvbuf(stdout, buf, _IOFBF, OUTPUT_BUFSIZE); return 0; } void out_deinit() { if(!flags[QUIET]) { if(flags[SYSLOG]) { closelog(); } } } int debug_print_flags() { int flag=0; printf_d("Debug: current flags: "); while(flag < (1<<8)) { if(flags[flag]) { int i=0; while(i++ < flags[flag]) printf_d("%c", flag); } flag++; } printf_d("\n"); return 0; } int printf_syslog_info(const char *fmt, ...) { va_list args; va_start(args, fmt); vsyslog(LOG_INFO, fmt, args); va_end(args); return 0; } int printf_syslog_debug(const char *fmt, ...) { va_list args; va_start(args, fmt); vsyslog(LOG_DEBUG, fmt, args); va_end(args); return 0; } int printf_syslog_err(const char *fmt, ...) { va_list args; va_start(args, fmt); vsyslog(LOG_ERR, fmt, args); va_end(args); return 0; } int printf_stderr(const char *fmt, ...) { int ret_print; va_list args; va_start(args, fmt); ret_print = vfprintf(stderr, fmt, args); va_end(args); return ret_print; } int write_syslog_info(const char *buf, size_t len) { // TODO: Consider with "len" syslog(LOG_INFO, "%s", buf); return 0; } int write_syslog_debug(const char *buf, size_t len) { // TODO: Consider with "len" syslog(LOG_DEBUG, "%s", buf); return 0; } int write_syslog_err(const char *buf, size_t len) { // TODO: Consider with "len" syslog(LOG_ERR, "%s", buf); return 0; } int write_stderr(const char *buf, size_t len) { return fwrite(buf, len, 1, stderr); } int write_stdout(const char *buf, size_t len) { return fwrite(buf, len, 1, stdout); } int printf_stdout(const char *fmt, ...) { va_list args; va_start(args, fmt); int ret_print = vprintf(fmt, args); va_end(args); return ret_print; } void out_flush() { fflush(stdout); } void hexdump_e(const unsigned char *buf, size_t len) { size_t i=0; while(i 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 out_init(int *flags); extern void out_flush(); extern void out_deinit(); extern int debug_print_flags(); typedef int (*printf_funct)(const char *fmt, ...); typedef int (*write_funct)(const char *buf, size_t len); extern printf_funct _printf_ddd; #define printf_ddd if(_printf_ddd!=NULL)_printf_ddd extern printf_funct _printf_dd; #define printf_dd if(_printf_dd!=NULL)_printf_dd extern printf_funct _printf_d; #define printf_d if(_printf_d!=NULL)_printf_d extern printf_funct _printf_v; #define printf_v if(_printf_v!=NULL)_printf_v extern write_funct _write_ddd; #define write_ddd if(_write_ddd!=null)_write_ddd extern write_funct _write_dd; #define write_dd if(_write_dd!=null)_write_dd extern write_funct _write_d; #define write_d if(_write_d!=NULL)_write_d extern write_funct _write_v; #define write_v if(_write_v!=NULL)_write_v extern printf_funct printf_e; extern printf_funct printf_out; extern write_funct write_e; extern write_funct write_out; extern int printf_syslog_err(const char *fmt, ...); extern int printf_syslog_debug(const char *fmt, ...); extern int printf_syslog_info(const char *fmt, ...); extern int write_syslog_err(const char *buf, size_t len); extern int write_syslog_debug(const char *buf, size_t len); extern int write_syslog_info(const char *buf, size_t len); extern int printf_stderr(const char *fmt, ...); extern int printf_stdout(const char *fmt, ...); extern int write_stderr(const char *buf, size_t len); extern int write_srdout(const char *buf, size_t len); #define write_out_s(buf) write_out(buf, sizeof(buf)-1) extern void hexdump_e(const unsigned char *buf, size_t len); extern void hexdump_d(const unsigned char *buf, size_t len); extern void hexdump_dd(const unsigned char *buf, size_t len); clsync-0.2.1/sync.c000066400000000000000000003171601222700035500141310ustar00rootroot00000000000000/* 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 "common.h" #include "main.h" #include "output.h" #include "fileutils.h" #include "malloc.h" #include "cluster.h" #include "sync.h" #include "glibex.h" #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++; } 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(eventinfo_t *evinfo_dst, eventinfo_t *evinfo_src) { printf_ddd("Debug3: evinfo_merge(): 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\n", 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 ); evinfo_dst->evmask |= evinfo_src->evmask; 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; } return; } static inline int _exitcode_process(options_t *options_p, int exitcode) { if(options_p->isignoredexitcode[(unsigned char)exitcode]) return 0; if(exitcode && !((options_p->flags[MODE]==MODE_RSYNCDIRECT) && (exitcode == 24))) { printf_e("Error: Got non-zero exitcode %i from __sync_exec().\n", exitcode); return exitcode; } return 0; } int exitcode_process(options_t *options_p, int exitcode) { int err = _exitcode_process(options_p, exitcode); if(err) printf_e("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)\n", exitcode, exitcode, strerror(exitcode)); return err; } /** * @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, ruleaction_t ruleaction, rule_t **rule_pp) { printf_ddd("Debug3: rules_search_getperm(\"%s\", %p, %p, %p, %p)\n", 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 printf_ddd("Debug3: rules_search_getperm(): Rules (p == %p):\n", rules_p); i=0; do { printf_ddd("\t%i\t%i\t%p/%p\n", 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) { printf_ddd("Debug3: rules_search_getperm(): Previous position is set.\n"); if(rule_p->mask == RA_NONE) return rule_p->perm; rule_p = ++(*rule_pp); i = rule_p->num; } printf_ddd("Debug3: rules_search_getperm(): Starting from position %i\n", i); while(rule_p->mask != RA_NONE) { printf_ddd("Debug3: rules_search_getperm(): %i -> %p/%p: type compare: %p, %p -> %p\n", 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 printf_ddd("Debug3: rules_search_getperm(): action-mask mismatch. Skipping.\n"); rule_p++;i++;// = &rules_p[++i]; continue; } if(rule_p->objtype && (rule_p->objtype != ftype)) { printf_ddd("Debug3: rules_search_getperm(): objtype mismatch. Skipping.\n"); rule_p++;i++;// = &rules_p[++i]; continue; } if(!regexec(&rule_p->expr, fpath, 0, NULL, 0)) break; printf_ddd("Debug3: rules_search_getperm(): doesn't match regex. Skipping.\n"); rule_p++;i++;// = &rules_p[++i]; } printf_dd("Debug2: matched to rule #%u for \"%s\":\t%p/%p (queried: %p).\n", 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; } static inline 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; printf_ddd("Debug3: rules_getperm(\"%s\", %p, %p (#%u), %p)\n", 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 } printf_ddd("Debug3: rules_getperm(\"%s\", %p, rules_p, %p): result perm is %p\n", fpath, (void *)(long)st_mode, (void *)(long)ruleactions, (void *)(long)resultperm); return resultperm; } // 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) { printf_e("Error: Cannot remove from index \"fpath2wd\" by wd %i.\n", wd); return -1; } ret |= g_hash_table_remove(indexes_p->fpath2wd_ht, fpath); return ret; } // 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) { printf_ddd("Debug3: indexes_add_wd(indexes_p, %i, \"%s\", %i)\n", 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; } // 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)); } // 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); } 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) { 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); printf_ddd("Debug3: indexes_queueevent(indexes_p, \"%s\", evinfo, %i). It's now %i events collected in queue %i.\n", 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) { // printf_ddd("Debug3: indexes_removefromqueue(indexes_p, \"%s\", %i).\n", fpath, queue_id); g_hash_table_remove(indexes_p->fpath2ei_coll_ht[queue_id], fpath); printf_ddd("Debug3: indexes_removefromqueue(indexes_p, \"%s\", %i). It's now %i events collected in queue %i.\n", 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)); printf_ddd("Debug3: indexes_addexclude(indexes_p, \"%s\", %i). It's now %i events collected in queue %i.\n", 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) { printf_ddd("Debug3: indexes_addexclude_aggr(indexes_p, \"%s\", %u).\n", 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)); printf_ddd("Debug3: indexes_addexclude_aggr(indexes_p, \"%s\", flags): %u.\n", 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)); printf_ddd("Debug3: indexes_outaggr_aggr(indexes_p, \"%s\").\n", outline); return 0; } static threadsinfo_t *thread_getinfo() { // 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)) { printf_e("Error: Cannot pthread_mutex_init(): %s (errno: %i).\n", strerror(errno), errno); return NULL; } if(pthread_cond_init (&threadsinfo.cond [i], NULL)) { printf_e("Error: Cannot pthread_cond_init(): %s (errno: %i).\n", strerror(errno), errno); return NULL; } i++; } threadsinfo.mutex_init++; } // pthread_mutex_lock(&threadsinfo._mutex); return &threadsinfo; } time_t thread_nextexpiretime() { time_t nextexpiretime = 0; threadsinfo_t *threadsinfo_p = thread_getinfo(); if(threadsinfo_p == NULL) return 0; int thread_num = threadsinfo_p->used; while(thread_num--) { threadinfo_t *threadinfo_p = &threadsinfo_p->threads[thread_num]; printf_ddd("Debug3: threadsinfo_p->threads[%i].state == %i;\tthreadsinfo_p->threads[%i].pthread == %p;\tthreadsinfo_p->threads[%i].expiretime == %i\n", 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; } } printf_ddd("Debug3: thread_nextexpiretime(): nextexpiretime == %i\n", nextexpiretime); return nextexpiretime; } threadinfo_t *thread_new() { threadsinfo_t *threadsinfo_p = thread_getinfo(); if(threadsinfo_p == NULL) return NULL; 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; printf_dd("Debug2: Reallocated memory for threadsinfo -> %i.\n", 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; printf_dd("Debug2: thread_new -> thread_num: %i; used: %i\n", thread_num, threadsinfo_p->used); return threadinfo_p; } int thread_del_bynum(int thread_num) { printf_dd("Debug2: thread_del_bynum(%i)\n", thread_num); threadsinfo_t *threadsinfo_p = thread_getinfo(); if(threadsinfo_p == NULL) return errno; if(thread_num >= threadsinfo_p->used) return 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--; printf_ddd("Debug3: thread_del_bynum(%i): there're %i threads left (#0).\n", thread_num, threadsinfo_p->used - threadsinfo_p->stacklen); return 0; } threadinfo_t *t = &threadsinfo_p->threads[threadsinfo_p->used-1]; if(t->state == STATE_EXIT) { threadsinfo_p->used--; printf_ddd("Debug3: thread_del_bynum(): %i [%p] -> %i [%p]; left: %i\n", 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) { printf_e("Error: thread_del_bynum(): Threads metadata structures pointers stack overflowed!"); return EINVAL; } #endif threadsinfo_p->threadsstack[threadsinfo_p->stacklen++] = threadinfo_p; } printf_ddd("Debug3: thread_del_bynum(%i): there're %i threads left (#1).\n", thread_num, threadsinfo_p->used - threadsinfo_p->stacklen); return 0; } int thread_gc(options_t *options_p) { int thread_num; time_t tm = time(NULL); printf_ddd("Debug3: thread_gc(): tm == %i; thread %p\n", tm, pthread_self()); if(!options_p->flags[PTHREAD]) return 0; threadsinfo_t *threadsinfo_p = thread_getinfo(); if(threadsinfo_p == NULL) return errno; printf_dd("Debug2: thread_gc(): There're %i threads.\n", threadsinfo_p->used); thread_num=-1; while(++thread_num < threadsinfo_p->used) { int err; threadinfo_t *threadinfo_p = &threadsinfo_p->threads[thread_num]; printf_ddd("Debug3: thread_gc(): Trying thread #%i (==%i) (state: %i; expire at: %i, now: %i, exitcode: %i, errcode: %i; i_p: %p; p: %p).\n", 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 printf_e("Debug3: thread_gc(): Thread #%i is alive too long: %lu <= %lu (started at %lu)\n", thread_num, threadinfo_p->expiretime, tm, threadinfo_p->starttime); return ETIME; } } #ifndef VERYPARANOID if(threadinfo_p->state != STATE_TERM) { printf_ddd("Debug3: thread_gc(): Thread #%i is busy, skipping (#0).\n", thread_num); continue; } #endif printf_ddd("Debug3: thread_gc(): Trying to join thread #%i: %p\n", 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: printf_ddd("Debug3: thread_gc(): Thread #%i is busy, skipping (#1).\n", thread_num); continue; #endif case EDEADLK: case EINVAL: case 0: printf_ddd("Debug3: thread_gc(): Thread #%i is finished with exitcode %i (errcode %i), deleting. threadinfo_p == %p\n", thread_num, threadinfo_p->exitcode, threadinfo_p->errcode, threadinfo_p); break; default: printf_e("Error: Got error while pthread_join() or pthread_tryjoin_np(): %s (errno: %i).\n", strerror(err), err); return errno; } if(threadinfo_p->errcode) { printf_e("Error: Got error from thread #%i: errcode %i.\n", thread_num, threadinfo_p->errcode); thread_del_bynum(thread_num); return threadinfo_p->errcode; } if(thread_del_bynum(thread_num)) return errno; } printf_ddd("Debug3: thread_gc(): There're %i threads left.\n", threadsinfo_p->used - threadsinfo_p->stacklen); return 0; } int thread_cleanup(options_t *options_p) { printf_ddd("Debug3: thread_cleanup(). Thread %p\n", pthread_self()); threadsinfo_t *threadsinfo_p = thread_getinfo(); if(threadsinfo_p == NULL) return errno; // Waiting for threads: printf_d("Debug: There're %i opened threads. Waiting.\n", 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); printf_d("Debug: killing pid %i with SIGTERM\n", threadinfo_p->child_pid); kill(threadinfo_p->child_pid, SIGTERM); pthread_join(threadinfo_p->pthread, NULL); printf_dd("Debug2: thread #%i exitcode: %i\n", threadsinfo_p->used, threadinfo_p->exitcode); /* if(threadinfo_p->callback) if((err=threadinfo_p->callback(options_p, threadinfo_p->argv))) printf_e("Warning: Got error from callback function: %s (errno: %i).\n", strerror(err), err); */ char **ptr = threadinfo_p->argv; while(*ptr) free(*(ptr++)); free(threadinfo_p->argv); } printf_ddd("Debug3: thread_cleanup(): All threads are closed.\n"); // 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 printf_ddd("Debug3: thread_cleanup(): done.\n"); return 0; } int *state_p = NULL; int exitcode = 0; int exec_argv(char **argv, int *child_pid DEBUGV(, int _rand)) { printf_ddd("Debug3: exec_argv(): Thread %p.\n", pthread_self()); pid_t pid; int status; // Forking pid = fork(); switch(pid) { case -1: printf_e("Error: Cannot fork(): %s (errno: %i).\n", strerror(errno), errno); return errno; case 0: execvp(argv[0], (char *const *)argv); return errno; } // printf_ddd("Debug3: exec_argv(): After fork thread %p"DEBUGV(" (%i)")".\n", pthread_self() DEBUGV(, _rand)); // 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 // printf_ddd("Debug3: exec_argv(): Pre-wait thread %p"DEBUGV(" (%i)")".\n", pthread_self() DEBUGV(, _rand)); if(waitpid(pid, &status, 0) != pid) { printf_e("Error: Cannot waitid(): %s (errno: %i).\n", strerror(errno), errno); return errno; } // printf_ddd("Debug3: exec_argv(): After-wait thread %p"DEBUGV(" (%i)")".\n", pthread_self() DEBUGV(, _rand)); #ifdef VERYPARANOID pthread_sigmask(SIG_SETMASK, &sigset_old, NULL); #endif // Return int exitcode = WEXITSTATUS(status); printf_ddd("Debug3: exec_argv(): execution completed with exitcode %i. Thread %p"DEBUGV(" (%i)")".\n", exitcode, pthread_self() DEBUGV(, _rand)); return exitcode; } static inline int thread_exit(threadinfo_t *threadinfo_p, int exitcode DEBUGV(, int _rand)) { int err=0; threadinfo_p->exitcode = exitcode; #if _DEBUG | VERYPARANOID if(threadinfo_p->pthread != pthread_self()) { printf_e("Error: thread_exit(): pthread id mismatch! (i_p->p) %p != (p) %p"DEBUGV("; rand == %i")"\n", threadinfo_p->pthread, pthread_self() DEBUGV(, _rand)); return EINVAL; } #endif if(threadinfo_p->callback) { if(threadinfo_p->options_p->flags[DEBUG]>2) { printf_ddd("Debug3: thread_exit(): thread %p, argv: \n", threadinfo_p->pthread); char **argv = threadinfo_p->argv; while(*argv) { printf_ddd("\t%p == %s\n", *argv, *argv); argv++; } } if((err=threadinfo_p->callback(threadinfo_p->options_p, threadinfo_p->argv))) { printf_e("Error: Got error from callback function: %s (errno: %i).\n", strerror(err), err); threadinfo_p->errcode = err; } } // Notifying the parent-thread, that it's time to collect garbage threads threadinfo_p->state = STATE_TERM; printf_ddd("Debug3: thread_exit(): thread %p is sending signal to sighandler to call GC\n", threadinfo_p->pthread); return pthread_kill(pthread_sighandler, SIGUSR_PTHREAD_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) { printf_e("Warning: so_call_sync_finished(): ei_i->path == NULL\n"); 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) { printf_ddd("Debug3: so_call_exec_thread(): thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p\n", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self()); options_t *options_p = threadinfo_p->options_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 = options_p->handler_funct.sync(n, ei); if((err=exitcode_process(threadinfo_p->options_p, rc))) { try_again = ((!options_p->retries) || (threadinfo_p->try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT); printf_e("Warning: so_call_sync_thread(): Bad exitcode %i (errcode %i). %s.\n", rc, err, try_again?"Retrying":"Give up"); if(try_again) { printf_dd("Debug2: so_call_sync_thread(): Sleeping for %u seconds before the retry.\n", options_p->syncdelay); sleep(options_p->syncdelay); } } } while(err && ((!options_p->retries) || (threadinfo_p->try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT)); if(err) { printf_e("Error: so_call_sync_thread(): Bad exitcode %i (errcode %i)\n", rc, err); threadinfo_p->errcode = err; } so_call_sync_finished(n, ei); if((err=thread_exit(threadinfo_p, rc DEBUGV(, 0)))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } return rc; } static inline int so_call_sync(options_t *options_p, indexes_t *indexes_p, int n, api_eventinfo_t *ei) { printf_dd("Debug2: so_call_sync(): n == %i\n", n); if(!options_p->flags[PTHREAD]) { int rc=0, ret=0, err=0; int try_n=0, try_again; do { try_again = 0; try_n++; alarm(options_p->synctimeout); rc = options_p->handler_funct.sync(n, ei); alarm(0); if((err=exitcode_process(options_p, rc))) { try_again = ((!options_p->retries) || (try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT); printf_e("Warning: so_call_sync(): Bad exitcode %i (errcode %i). %s.\n", rc, err, try_again?"Retrying":"Give up"); if(try_again) { printf_dd("Debug2: so_call_sync(): Sleeping for %u seconds before the retry.\n", options_p->syncdelay); sleep(options_p->syncdelay); } } } while(err && ((!options_p->retries) || (try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT)); if(err) { printf_e("Error: so_call_sync(): Bad exitcode %i (errcode %i)\n", rc, err); ret = err; } 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->options_p = options_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; if(options_p->synctimeout) threadinfo_p->expiretime = threadinfo_p->starttime + options_p->synctimeout; if(pthread_create(&threadinfo_p->pthread, NULL, (void *(*)(void *))so_call_sync_thread, threadinfo_p)) { printf_e("Error: Cannot pthread_create(): %s (errno: %i).\n", strerror(errno), errno); return errno; } printf_ddd("Debug3: so_call_sync(): thread %p\n", threadinfo_p->pthread); return 0; } static inline int so_call_rsync_finished(options_t *options_p, const char *inclistfile, const char *exclistfile) { int ret0, ret1; if(inclistfile == NULL) { printf_e("Error: inclistfile == NULL."); return EINVAL; } printf_ddd("Debug3: unlink()-ing \"%s\"\n", inclistfile); ret0 = unlink(inclistfile); if(options_p->flags[RSYNCPREFERINCLUDE]) return ret0; if(exclistfile == NULL) { printf_e("Error: exclistfile == NULL."); return EINVAL; } printf_ddd("Debug3: unlink()-ing \"%s\"\n", exclistfile); ret1 = unlink(exclistfile); return ret0 == 0 ? ret1 : ret0; } int so_call_rsync_thread(threadinfo_t *threadinfo_p) { printf_ddd("Debug3: so_call_exec_thread(): thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p\n", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self()); options_t *options_p = threadinfo_p->options_p; char **argv = threadinfo_p->argv; int err=0, rc=0, try_again; do { try_again=0; threadinfo_p->try_n++; rc = options_p->handler_funct.rsync(argv[0], argv[1]); if((err=exitcode_process(threadinfo_p->options_p, rc))) { try_again = ((!options_p->retries) || (threadinfo_p->try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT); printf_e("Warning: so_call_rsync_thread(): Bad exitcode %i (errcode %i). %s.\n", rc, err, try_again?"Retrying":"Give up"); if(try_again) { printf_dd("Debug2: so_call_rsync_thread(): Sleeping for %u seconds before the retry.\n", options_p->syncdelay); sleep(options_p->syncdelay); } } } while(try_again); if(err) { printf_e("Error: so_call_rsync_thread(): Bad exitcode %i (errcode %i)\n", rc, err); threadinfo_p->errcode = err; } if((err=so_call_rsync_finished(options_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 DEBUGV(, 0)))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } return rc; } static inline int so_call_rsync(options_t *options_p, indexes_t *indexes_p, const char *inclistfile, const char *exclistfile) { printf_dd("Debug2: so_call_rsync(): inclistfile == \"%s\"; exclistfile == \"%s\"\n", inclistfile, exclistfile); if(!options_p->flags[PTHREAD]) { printf_ddd("Debug3: so_call_rsync(): options_p->handler_funct.rsync == %p\n", options_p->handler_funct.rsync); int rc=0, err=0; int try_n=0, try_again; do { try_again = 0; try_n++; alarm(options_p->synctimeout); rc = options_p->handler_funct.rsync(inclistfile, exclistfile); alarm(0); if((err=exitcode_process(options_p, rc))) { try_again = ((!options_p->retries) || (try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT); printf_e("Warning: so_call_rsync(): Bad exitcode %i (errcode %i). %s.\n", rc, err, try_again?"Retrying":"Give up"); if(try_again) { printf_dd("Debug2: so_call_rsync(): Sleeping for %u seconds before the retry.\n", options_p->syncdelay); sleep(options_p->syncdelay); } } } while(try_again); if(err) { printf_e("Error: so_call_rsync(): Bad exitcode %i (errcode %i)\n", rc, err); rc = err; } int ret_cleanup; if((ret_cleanup=so_call_rsync_finished(options_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->options_p = options_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->argv[0] = strdup(inclistfile); threadinfo_p->argv[1] = strdup(exclistfile); if(options_p->synctimeout) threadinfo_p->expiretime = threadinfo_p->starttime + options_p->synctimeout; if(pthread_create(&threadinfo_p->pthread, NULL, (void *(*)(void *))so_call_rsync_thread, threadinfo_p)) { printf_e("Error: Cannot pthread_create(): %s (errno: %i).\n", strerror(errno), errno); return errno; } printf_ddd("Debug3: so_call_rsync(): thread %p\n", threadinfo_p->pthread); return 0; } // === SYNC_EXEC() === { #define SYNC_EXEC(...) (options_p->flags[PTHREAD]?sync_exec_thread:sync_exec)(__VA_ARGS__) #define _sync_exec_getargv(argv, firstarg, COPYARG) {\ va_list arglist;\ va_start(arglist, firstarg);\ \ int i = 0;\ do {\ char *arg;\ if(i >= MAXARGUMENTS) {\ printf_e("Error: Too many arguments (%i >= %i).\n", i, MAXARGUMENTS);\ return ENOMEM;\ }\ arg = (char *)va_arg(arglist, const char *const);\ argv[i] = arg!=NULL ? COPYARG : NULL;\ \ printf_dd("Debug2: argv[%i] = %s\n", i, argv[i]);\ } while(argv[i++] != NULL);\ va_end(arglist);\ } char *sync_path_abs2rel(options_t *options_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 = (options_p->watchdir == options_p->watchdirwslash /* if watch-dir == "/" */) ? 0 : options_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; if(path_rel_oldptr == NULL) { path_rel = xmalloc(path_abs_len+1); } else { if(path_rel_len > *path_rel_len_p) { path_rel = xrealloc(path_rel_oldptr, path_rel_len+1); } else { path_rel = 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 printf_ddd("Debug3: sync_path_abs2rel(): \"%s\" (len: %i) --%i--> \"%s\" (len: %i) + ", path_abs, path_abs_len, path_rel[path_rel_len - 1] == '/', options_p->watchdirwslash, watchdirlen+1); if(path_rel[path_rel_len - 1] == '/') path_rel[--path_rel_len] = 0x00; printf_ddd("\"%s\" (len: %i)\n", 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(options_t *options_p) { // if(options_p->flags[PTHREAD]) // return fork(); // Cleaning stale pids. TODO: Optimize this. Remove this GC. int i=0; while(i < options_p->children) { if(waitpid(options_p->child_pid[i], NULL, WNOHANG)<0) if(errno==ECHILD) options_p->child_pid[i] = options_p->child_pid[--options_p->children]; i++; } // Too many children if(options_p->children >= MAXCHILDREN) { errno = ECANCELED; return -1; } // Forking pid_t pid = fork(); options_p->child_pid[options_p->children++] = pid; return pid; } static inline int sync_exec(options_t *options_p, indexes_t *indexes_p, thread_callbackfunct_t callback, ...) { printf_dd("Debug2: sync_exec()\n"); char **argv = (char **)xcalloc(sizeof(char *), MAXARGUMENTS); memset(argv, 0, sizeof(char *)*MAXARGUMENTS); _sync_exec_getargv(argv, callback, arg); int exitcode=0, ret=0, err=0; int try_n=0, try_again; do { try_again = 0; try_n++; printf_dd("Debug2: sync_exec(): try_n == %u (retries == %u)\n", try_n, options_p->retries); alarm(options_p->synctimeout); options_p->children = 1; exitcode = exec_argv(argv, options_p->child_pid DEBUGV(, 0)); options_p->children = 0; alarm(0); if((err=exitcode_process(options_p, exitcode))) { try_again = ((!options_p->retries) || (try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT); printf_e("Warning: sync_exec(): Bad exitcode %i (errcode %i). %s.\n", exitcode, err, try_again?"Retrying":"Give up"); if(try_again) { printf_dd("Debug2: sync_exec(): Sleeping for %u seconds before the retry.\n", options_p->syncdelay); sleep(options_p->syncdelay); } } } while(try_again); if(err) { printf_e("Error: sync_exec(): Bad exitcode %i (errcode %i)\n", exitcode, err); ret = err; // goto l_sync_exec_end; } if(callback != NULL) { int nret = callback(options_p, argv); if(nret) { printf_e("Error: Got error while callback(): %s (errno: %i).\n", strerror(ret), ret); if(!ret) ret=nret; // goto l_sync_exec_end; } } //l_sync_exec_end: free(argv); return ret; } int __sync_exec_thread(threadinfo_t *threadinfo_p) { char **argv = threadinfo_p->argv; options_t *options_p = threadinfo_p->options_p; #ifdef _DEBUG int _rand=rand(); #endif printf_ddd("Debug3: __sync_exec_thread(): thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p"DEBUGV("; rand == %i")"\n", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self() DEBUGV(, _rand)); 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 DEBUGV(, _rand)); if((err=exitcode_process(threadinfo_p->options_p, exec_exitcode))) { try_again = ((!options_p->retries) || (threadinfo_p->try_n < options_p->retries)) && (*state_p != STATE_TERM) && (*state_p != STATE_EXIT); printf_e("Warning: __sync_exec_thread(): Bad exitcode %i (errcode %i). %s.\n", exec_exitcode, err, try_again?"Retrying":"Give up"); if(try_again) { printf_dd("Debug2: __sync_exec_thread(): Sleeping for %u seconds before the retry.\n", options_p->syncdelay); sleep(options_p->syncdelay); } } } while(try_again); if(err) { printf_e("Error: __sync_exec_thread(): Bad exitcode %i (errcode %i)\n", exec_exitcode, err); threadinfo_p->errcode = err; } g_hash_table_destroy(threadinfo_p->fpath2ei_ht); if((err=thread_exit(threadinfo_p, exec_exitcode DEBUGV(, _rand)))) { exitcode = err; // This's global variable "exitcode" pthread_kill(pthread_sighandler, SIGTERM); } printf_ddd("Debug3: __sync_exec_thread(): thread_num == %i; threadinfo_p == %p; i_p->pthread %p; thread %p"DEBUGV("; rand == %i")"; errcode %i\n", threadinfo_p->thread_num, threadinfo_p, threadinfo_p->pthread, pthread_self(), DEBUGV(_rand,) threadinfo_p->errcode); return exec_exitcode; } static inline int sync_exec_thread(options_t *options_p, indexes_t *indexes_p, thread_callbackfunct_t callback, ...) { printf_dd("Debug2: sync_exec_thread()\n"); char **argv = (char **)xcalloc(sizeof(char *), MAXARGUMENTS); memset(argv, 0, sizeof(char *)*MAXARGUMENTS); _sync_exec_getargv(argv, callback, strdup(arg)); threadinfo_t *threadinfo_p = thread_new(); if(threadinfo_p == NULL) return errno; threadinfo_p->try_n = 0; threadinfo_p->callback = callback; threadinfo_p->argv = argv; threadinfo_p->options_p = options_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); if(options_p->synctimeout) threadinfo_p->expiretime = threadinfo_p->starttime + options_p->synctimeout; if(pthread_create(&threadinfo_p->pthread, NULL, (void *(*)(void *))__sync_exec_thread, threadinfo_p)) { printf_e("Error: Cannot pthread_create(): %s (errno: %i).\n", strerror(errno), errno); return errno; } printf_ddd("Debug3: sync_exec_thread(): thread %p\n", threadinfo_p->pthread); return 0; } // } === SYNC_EXEC() === static int sync_queuesync(const char *fpath_rel, eventinfo_t *evinfo, options_t *options_p, indexes_t *indexes_p, queue_id_t queue_id) { printf_ddd("Debug3: sync_queuesync(\"%s\", ...): fsize == %lu; tres == %lu, queue_id == %u\n", fpath_rel, evinfo->fsize, options_p->bfilethreshold, queue_id); if(queue_id == QUEUE_AUTO) queue_id = (evinfo->fsize > options_p->bfilethreshold) ? QUEUE_BIGFILE : QUEUE_NORMAL; queueinfo_t *queueinfo = &options_p->_queues[queue_id]; if(!queueinfo->stime) queueinfo->stime = time(NULL); // char *fpath_rel = sync_path_abs2rel(options_p, fpath, -1, NULL, NULL); // Filename can contain "\n" 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 :( printf_ddd("Debug3: sync_queuesync(): There's \"\\n\" character in path \"%s\". Ignoring it :(. Feedback to: https://github.com/xaionaro/clsync/issues/12\n", fpath_rel); return 0; } #ifdef CLUSTER_SUPPORT if(options_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(evinfo_q, evinfo); } return 0; } int sync_initialsync_walk(options_t *options_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 = options_p->rules; printf_dd("Debug2: sync_initialsync_walk(options_p, \"%s\", indexes_p, %i, %i).\n", dirpath, queue_id, initsync); char skip_rules = (initsync==INITSYNC_FULL) && options_p->flags[INITFULL]; char rsync_and_prefer_excludes = ( (options_p->flags[MODE]==MODE_RSYNCDIRECT) || (options_p->flags[MODE]==MODE_RSYNCSHELL) || (options_p->flags[MODE]==MODE_RSYNCSO) ) && !options_p->flags[RSYNCPREFERINCLUDE]; if((!options_p->flags[RSYNCPREFERINCLUDE]) && skip_rules) return 0; char fts_no_stat = (initsync==INITSYNC_FULL) && !(options_p->flags[EXCLUDEMOUNTPOINTS]); int fts_opts = FTS_NOCHDIR | FTS_PHYSICAL | (fts_no_stat ? FTS_NOSTAT : 0) | (options_p->flags[ONEFILESYSTEM] ? FTS_XDEV : 0); printf_ddd("Debug3: sync_initialsync_walk() fts_opts == %p\n", (void *)(long)fts_opts); tree = fts_open((char *const *)&rootpaths, fts_opts, NULL); if(tree == NULL) { printf_e("Error: Cannot fts_open() on \"%s\": %s (errno: %i).\n", dirpath, strerror(errno), errno); return errno; } memset(&evinfo, 0, sizeof(evinfo)); FTSENT *node; char *path_rel = NULL; size_t path_rel_len = 0; while((node = fts_read(tree))) { 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: if(node->fts_errno == ENOENT) { printf_d("Debug: Got error while fts_read(): %s (errno: %i; fts_info: %i).\n", strerror(node->fts_errno), node->fts_errno, node->fts_info); continue; } else { printf_e("Error: Got error while fts_read(): %s (errno: %i; fts_info: %i).\n", strerror(node->fts_errno), node->fts_errno, node->fts_info); ret = node->fts_errno; goto l_sync_initialsync_walk_end; } default: printf_e("Error: Got unknown fts_info vlaue while fts_read(): %i.\n", node->fts_info); ret = EINVAL; goto l_sync_initialsync_walk_end; } path_rel = sync_path_abs2rel(options_p, node->fts_path, -1, &path_rel_len, path_rel); printf_ddd("Debug3: sync_initialsync_walk(): Pointing to \"%s\" (node->fts_info == %i)\n", path_rel, node->fts_info); if(options_p->flags[EXCLUDEMOUNTPOINTS] && node->fts_info==FTS_D) { if(rsync_and_prefer_excludes) { if(node->fts_statp->st_dev != options_p->st_dev) { if(queue_id == QUEUE_AUTO) { int i=0; while(ifts_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)) { printf_ddd("Debug3: sync_initialsync_walk(): Rejecting to walk into \"%s\".\n", path_rel); fts_set(tree, node, FTS_SKIP); } if(!(perm&RA_MONITOR)) { printf_ddd("Debug3: sync_initialsync_walk(): Excluding \"%s\".\n", path_rel); if(rsync_and_prefer_excludes) { if(queue_id == QUEUE_AUTO) { int i=0; while(ifts_info==FTS_D ? EOT_DIR : EOT_FILE; evinfo.fsize = fts_no_stat ? 0 : node->fts_statp->st_size; switch(options_p->notifyengine) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: break; #endif case NE_INOTIFY: evinfo.evmask = IN_CREATE_SELF; if(node->fts_info==FTS_D) { evinfo.evmask |= IN_ISDIR; } break; } if(!rsync_and_prefer_excludes) { printf_ddd("Debug2: sync_initialsync_walk(): queueing \"%s\" (depth: %i) with int-flags %p\n", node->fts_path, node->fts_level, (void *)(unsigned long)evinfo.flags); int _ret = sync_queuesync(path_rel, &evinfo, options_p, indexes_p, queue_id); if(_ret) { printf_e("Error: Got error while queueing \"%s\": %s (errno: %i).\n", node->fts_path, strerror(errno), errno); ret = errno; goto l_sync_initialsync_walk_end; } } } if(errno) { printf_e("Error: Got error while fts_read() and related routines: %s (errno: %i).\n", strerror(errno), errno); ret = errno; goto l_sync_initialsync_walk_end; } if(fts_close(tree)) { printf_e("Error: Got error while fts_close(): %s (errno: %i).\n", strerror(errno), errno); ret = errno; goto l_sync_initialsync_walk_end; } l_sync_initialsync_walk_end: if(path_rel != NULL) free(path_rel); return ret; } int sync_initialsync(const char *path, options_t *options_p, indexes_t *indexes_p, initsync_t initsync) { printf_ddd("Debug3: sync_initialsync(\"%s\", options_p, indexes_p, %i)\n", path, initsync); #ifdef CLUSTER_SUPPORT if(initsync == INITSYNC_FULL) { if(options_p->cluster_iface) return cluster_initialsync(); } #endif queue_id_t queue_id = (initsync==INITSYNC_FULL) ? QUEUE_INSTANT : QUEUE_NORMAL; // non-RSYNC case: if( !( (options_p->flags[MODE]==MODE_RSYNCDIRECT) || (options_p->flags[MODE]==MODE_RSYNCSHELL) || (options_p->flags[MODE]==MODE_RSYNCSO) ) ) { printf_ddd("Debug3: sync_initialsync(): syncing \"%s\"\n", path); /* if(options_p->flags[PTHREAD]) return sync_exec_thread(options_p, NULL, options_p->handlerfpath, "initialsync", options_p->label, path, NULL); else return sync_exec (options_p, NULL, options_p->handlerfpath, "initialsync", options_p->label, path, NULL);*/ if(options_p->flags[HAVERECURSIVESYNC]) { if(options_p->flags[MODE] == MODE_SO) { api_eventinfo_t *ei = (api_eventinfo_t *)xmalloc(sizeof(*ei)); #ifdef PARANIOD memset(ei, 0, sizeof(*ei)); #endif ei->evmask = IN_CREATE|IN_ISDIR; ei->flags = EVIF_RECURSIVELY; ei->path_len = strlen(path); ei->path = strdup(path); ei->objtype_old = EOT_DOESNTEXIST; ei->objtype_new = EOT_DIR; return so_call_sync(options_p, indexes_p, 1, ei); } else { return SYNC_EXEC( options_p, indexes_p, NULL, options_p->handlerfpath, "initialsync", options_p->label, path, NULL ); } } #ifdef DOXYGEN sync_exec(NULL, NULL); sync_exec_thread(NULL, NULL); #endif int ret = sync_initialsync_walk(options_p, path, indexes_p, queue_id, initsync); if(ret) printf_e("Error: sync_initialsync(): Cannot get synclist: %s (errno: %i)\n", strerror(ret), ret); return ret; } // RSYNC case: if(!options_p->flags[RSYNCPREFERINCLUDE]) { queueinfo_t *queueinfo = &options_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 int ret = sync_initialsync_walk(options_p, path, indexes_p, queue_id, initsync); if(ret) { printf_e("Error: sync_initialsync(): Cannot get exclude what to exclude: %s (errno: %i)\n", strerror(ret), ret); return ret; } printf_ddd("Debug3: sync_initialsync(): queueing \"%s\" with int-flags %p\n", path, (void *)(unsigned long)evinfo->flags); char *path_rel = sync_path_abs2rel(options_p, path, -1, NULL, NULL); return indexes_queueevent(indexes_p, path_rel, evinfo, queue_id); } // Searching for includes return sync_initialsync_walk(options_p, path, indexes_p, queue_id, initsync); } int sync_notify_mark(int notify_d, options_t *options_p, const char *accpath, const char *path, size_t pathlen, indexes_t *indexes_p) { printf_ddd("Debug3: sync_notify_mark(..., \"%s\", %i,...)\n", path, pathlen); int wd = indexes_fpath2wd(indexes_p, path); if(wd != -1) { printf_d("Debug: \"%s\" is already marked (wd: %i). Skipping.\n", path, wd); return wd; } switch(options_p->notifyengine) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: { int fanotify_d = notify_d; if((wd = fanotify_mark(fanotify_d, FAN_MARK_ADD | FAN_MARK_DONT_FOLLOW, FANOTIFY_MARKMASK, AT_FDCWD, accpath)) == -1) { if(errno == ENOENT) return -2; printf_e("Error: Cannot fanotify_mark() on \"%s\": %s (errno: %i).\n", path, strerror(errno), errno); return -1; } break; } #endif case NE_INOTIFY: { int inotify_d = notify_d; if((wd = inotify_add_watch(inotify_d, accpath, INOTIFY_MARKMASK)) == -1) { if(errno == ENOENT) return -2; printf_e("Error: Cannot inotify_add_watch() on \"%s\": %s (errno: %i).\n", path, strerror(errno), errno); return -1; } break; } default: { printf_e("Error: unknown notify-engine: %i\n", options_p->notifyengine); errno = EINVAL; return -1; } } indexes_add_wd(indexes_p, wd, path, pathlen); return wd; } #ifdef CLUSTER_SUPPORT static inline int sync_mark_walk_cluster_modtime_update(options_t *options_p, const char *path, short int dirlevel, mode_t st_mode) { if(options_p->cluster_iface) { int ret=cluster_modtime_update(path, dirlevel, st_mode); if(ret) printf_e("Error: sync_mark_walk() cannot cluster_modtime_update(): %s (errno %i)\n", strerror(ret), ret); return ret; } return 0; } #endif int sync_mark_walk(int notify_d, options_t *options_p, const char *dirpath, indexes_t *indexes_p) { int ret = 0; const char *rootpaths[] = {dirpath, NULL}; FTS *tree; rule_t *rules_p = options_p->rules; printf_dd("Debug2: sync_mark_walk(%i, options_p, \"%s\", indexes_p).\n", notify_d, dirpath); printf_funct my_printf_e = STATE_STARTING(state_p) ? printf_e : _printf_dd; int fts_opts = FTS_NOCHDIR|FTS_PHYSICAL|FTS_NOSTAT|(options_p->flags[ONEFILESYSTEM]?FTS_XDEV:0); printf_ddd("Debug3: sync_mark_walk() fts_opts == %p\n", (void *)(long)fts_opts); tree = fts_open((char *const *)&rootpaths, fts_opts, NULL); if(tree == NULL) { my_printf_e("Error: Cannot fts_open() on \"%s\": %s (errno: %i).\n", dirpath, strerror(errno), errno); return errno; } FTSENT *node; char *path_rel = NULL; size_t path_rel_len = 0; while((node = fts_read(tree))) { #ifdef CLUSTER_SUPPORT int ret; #endif printf_dd("Debug3: walking: \"%s\" (depth %u): fts_info == %i\n", node->fts_path, node->fts_level, node->fts_info); switch(node->fts_info) { // Duplicates: case FTS_DP: continue; 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(options_p, node->fts_path, node->fts_level, S_IFREG))) goto l_sync_mark_walk_end; #endif continue; // 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(options_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) { printf_d("Debug: Got error while fts_read(): %s (errno: %i; fts_info: %i).\n", strerror(errno), errno, node->fts_info); continue; } else { my_printf_e("Error: Got error while fts_read(): %s (errno: %i; fts_info: %i).\n", strerror(errno), errno, node->fts_info); ret = errno; goto l_sync_mark_walk_end; } default: my_printf_e("Error: Got unknown fts_info vlaue while fts_read(): %i.\n", node->fts_info); ret = EINVAL; goto l_sync_mark_walk_end; } path_rel = sync_path_abs2rel(options_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); if(!(perm&RA_WALK)) { fts_set(tree, node, FTS_SKIP); continue; } printf_dd("Debug2: marking \"%s\" (depth %u)\n", node->fts_path, node->fts_level); int wd = sync_notify_mark(notify_d, options_p, node->fts_accpath, node->fts_path, node->fts_pathlen, indexes_p); if(wd == -1) { my_printf_e("Error: Got error while notify-marking \"%s\": %s (errno: %i).\n", node->fts_path, strerror(errno), errno); ret = errno; goto l_sync_mark_walk_end; } printf_dd("Debug2: watching descriptor is %i.\n", wd); } if(errno) { my_printf_e("Error: Got error while fts_read() and related routines: %s (errno: %i).\n", strerror(errno), errno); ret = errno; goto l_sync_mark_walk_end; } if(fts_close(tree)) { my_printf_e("Error: Got error while fts_close(): %s (errno: %i).\n", strerror(errno), errno); 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(options_t *options_p) { switch(options_p->notifyengine) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: { int fanotify_d = fanotify_init(FANOTIFY_FLAGS, FANOTIFY_EVFLAGS); if(fanotify_d == -1) { printf_e("Error: cannot fanotify_init(%i, %i): %s (errno: %i).\n", FANOTIFY_FLAGS, FANOTIFY_EVFLAGS, strerror(errno), errno); return -1; } return fanotify_d; } #endif case NE_INOTIFY: { #ifdef OLDSYSTEM int inotify_d = inotify_init(); #else int inotify_d = inotify_init1(INOTIFY_FLAGS); #endif if(inotify_d == -1) { printf_e("Error: cannot inotify_init(%i): %s (errno: %i).\n", INOTIFY_FLAGS, strerror(errno), errno); return -1; } return inotify_d; } } printf_e("Error: unknown notify-engine: %i\n", options_p->notifyengine); errno = EINVAL; return -1; } static inline int sync_dosync_exec(options_t *options_p, indexes_t *indexes_p, const char *evmask_str, const char *fpath) { /* if(options_p->flags[PTHREAD]) return sync_exec_thread(options_p, NULL, options_p->handlerfpath, "sync", options_p->label, evmask_str, fpath, NULL); else return sync_exec (options_p, NULL, options_p->handlerfpath, "sync", options_p->label, evmask_str, fpath, NULL);*/ return SYNC_EXEC(options_p, indexes_p, NULL, options_p->handlerfpath, "sync", options_p->label, evmask_str, fpath, NULL); #ifdef DOXYGEN sync_exec(NULL, NULL); sync_exec_thread(NULL, NULL); #endif } static int sync_dosync(const char *fpath, uint32_t evmask, options_t *options_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(options_p, indexes_p, evmask_str, fpath); free(evmask_str); #ifdef CLUSTER_SUPPORT ret = cluster_unlock_all(); #endif return ret; } 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; printf_ddd("Debug3: _sync_idle_dosync_collectedexcludes(): \"%s\", %u (%p).\n", fpath, GPOINTER_TO_INT(flags_gp), flags_gp); indexes_addexclude_aggr(indexes_p, strdup(fpath), (eventinfo_flags_t)GPOINTER_TO_INT(flags_gp)); return; } 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; // FILE *outf = ((struct dosync_arg *)arg_gp)->outf; options_t *options_p = ((struct dosync_arg *)arg_gp)->options_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; printf_ddd("Debug3: _sync_idle_dosync_collectedevents(): queue_id == %i.\n", queue_id); if((options_p->listoutdir == NULL) && (!(options_p->flags[MODE]==MODE_SO))) { printf_ddd("Debug3: _sync_idle_dosync_collectedevents(): calling sync_dosync()\n"); int ret; if((ret=sync_dosync(fpath, evinfo->evmask, options_p, indexes_p))) { printf_e("Error: unable to sync \"%s\" (evmask %i): %s (errno: %i).\n", fpath, evinfo->evmask, strerror(ret), ret); exit(ret); // TODO: remove this from here } } 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(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(evinfo_idx, evinfo_q); indexes_removefromqueue(indexes_p, fpath, _queue_id); if(!indexes_queuelen(indexes_p, _queue_id)) options_p->_queues[_queue_id].stime = 0; } _queue_id++; } if(isnew) indexes_fpath2ei_add(indexes_p, strdup(fpath), evinfo_idx); else free(fpath); return; } int sync_idle_dosync_collectedevents_cleanup(options_t *options_p, char **argv) { if(options_p->flags[DONTUNLINK]) return 0; printf_ddd("Debug3: sync_idle_dosync_collectedevents_cleanup(): thread %p\n", pthread_self()); if(options_p->flags[MODE] == MODE_RSYNCDIRECT) { int ret0, ret1; if(argv[5] == NULL) { printf_e("Error: Unexpected *argv[] end."); return EINVAL; } printf_ddd("Debug3: unlink()-ing \"%s\"\n", argv[5]); ret0 = unlink(argv[5]); if(options_p->flags[RSYNCPREFERINCLUDE]) return ret0; if(argv[7] == NULL) { printf_e("Error: Unexpected *argv[] end."); return EINVAL; } printf_ddd("Debug3: unlink()-ing \"%s\"\n", argv[7]); ret1 = unlink(argv[7]); return ret0 == 0 ? ret1 : ret0; } if(argv[3] == NULL) { printf_e("Error: Unexpected *argv[] end."); return EINVAL; } int ret0; printf_ddd("Debug3: unlink()-ing \"%s\"\n", argv[3]); ret0 = unlink(argv[3]); if(options_p->flags[MODE] == MODE_RSYNCSHELL) { int ret1; // There's no exclude file-list if "--rsyncpreferinclude" is enabled, so return if(options_p->flags[RSYNCPREFERINCLUDE]) return ret0; if(argv[4] == NULL) return ret0; if(*argv[4] == 0x00) return ret0; printf_ddd("Debug3: unlink()-ing \"%s\"\n", argv[4]); ret1 = unlink(argv[4]); // remove exclude list, too return ret0 == 0 ? ret1 : ret0; } return ret0; } int sync_idle_dosync_collectedevents_aggrqueue(queue_id_t queue_id, options_t *options_p, indexes_t *indexes_p, struct dosync_arg *dosync_arg) { // char *buf, *fpath; time_t tm = time(NULL); queueinfo_t *queueinfo = &options_p->_queues[queue_id]; if((queueinfo->stime + queueinfo->collectdelay > tm) && (queueinfo->collectdelay != COLLECTDELAY_INSTANT) && (!options_p->flags[EXITONNOEVENTS])) { printf_ddd("Debug3: sync_idle_dosync_collectedevents_procqueue(%i, ...): too early (%i + %i > %i).\n", 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]); printf_ddd("Debug3: sync_idle_dosync_collectedevents_procqueue(%i, ...): evcount_real == %i\n", queue_id, evcount_real); if(evcount_real<=0) { printf_ddd("Debug3: sync_idle_dosync_collectedevents_procqueue(%i, ...): no events, return 0.\n", queue_id); return 0; } 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(!options_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]); } return 0; } int sync_idle_dosync_collectedevents_uniqfname(options_t *options_p, char *fpath, char *name) { pid_t pid = getpid(); time_t tm = time(NULL); struct stat64 stat64; int counter = 0; do { snprintf(fpath, PATH_MAX, "%s/.clsync-%s.%u.%lu.%lu.%u", options_p->listoutdir, name, pid, (long)pthread_self(), (unsigned long)tm, rand()); // To be unique lstat64(fpath, &stat64); if(counter++ > COUNTER_LIMIT) { printf_e("Error: Cannot file unused filename for list-file. The last try was \"%s\".\n", fpath); return ELOOP; } } 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) { printf_ddd("Debug3: Creating %s file\n", name); char *fpath = dosync_arg_p->outf_path; options_t *options_p = dosync_arg_p->options_p; int ret; if((ret=sync_idle_dosync_collectedevents_uniqfname(options_p, fpath, name))) { printf_e("Error: sync_idle_dosync_collectedevents_listcreate: Cannot get unique file name.\n"); return ret; } dosync_arg_p->outf = fopen(fpath, "w"); if(dosync_arg_p->outf == NULL) { printf_e("Error: Cannot open \"%s\" as file for writing: %s (errno: %i).\n", fpath, strerror(errno), errno); return errno; } setbuffer(dosync_arg_p->outf, dosync_arg_p->buf, BUFSIZ); printf_ddd("Debug3: Created list-file \"%s\"\n", 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_coords_size = ALLOC_PORTION; // size_t *sc_coords = malloc(sizeof(*sc_coords) * sc_coords_size); 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 '\\': /* if(sc_count >= sc_coords_size-1) { sc_coords_size += ALLOC_PORTION; sc_coords = realloc(sc_coords, sizeof(*sc_coords) * sc_coords_size); } sc_coords[sc_count++] = i; */ 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 = realloc(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] = '\\'; // if(!sc_count) // goto l_rsync_escape_loop1_end; break; } } /* size_t end = i+sc_count; char *from, *to; sc_coords[sc_count] = end; while(sc_count) { char *from, *to; sc_count--; to = &path[sc_coords[sc_count]+sc_count]; from = &path[sc_coords[sc_count]+1]; memmove(to, from, sc_coords[sc_count+1]-sc_coords[sc_count]-1); } */ //l_rsync_escape_loop1_end: return rsync_escape_result; } static inline int rsync_outline(FILE *outf, char *outline, eventinfo_flags_t flags) { if(flags & EVIF_RECURSIVELY) { printf_ddd("Debug3: rsync_aggrout(): Recursively \"%s\": Writing to rsynclist: \"%s/***\".\n", outline, outline); fprintf(outf, "%s/***\n", outline); } else if(flags & EVIF_CONTENTRECURSIVELY) { printf_ddd("Debug3: rsync_aggrout(): Content-recursively \"%s\": Writing to rsynclist: \"%s/**\".\n", outline, outline); fprintf(outf, "%s/**\n", outline); } else { printf_ddd("Debug3: rsync_aggrout(): Non-recursively \"%s\": Writing to rsynclist: \"%s\".\n", outline, outline); fprintf(outf, "%s\n", outline); } 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); // printf_ddd("Debug3: rsync_aggrout(): \"%s\"\n", outline); int ret; if((ret=rsync_outline(outf, outline, flags))) { printf_e("Error: rsync_aggrout(): Got error from rsync_outline(). Exit.\n"); 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; printf_ddd("Debug3: rsync_listpush(): \"%s\": Adding to rsynclist: \"%s\" with flags %p.\n", 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; printf_ddd("Debug3: rsync_listpush(): Non-recursively \"%s\": Adding to rsynclist: \"%s\".\n", 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); // options_t *options_p = dosync_arg_p->options_p; // indexes_t *indexes_p = dosync_arg_p->indexes_p; printf_ddd("Debug3: sync_idle_dosync_collectedevents_rsync_exclistpush(): \"%s\"\n", 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))) { printf_e("Error: sync_idle_dosync_collectedevents_rsync_exclistpush(): Got error from rsync_outline(). Exit.\n"); exit(ret); // TODO: replace this with kill(0, ...) } return TRUE; } int sync_idle_dosync_collectedevents_commitpart(struct dosync_arg *dosync_arg_p) { options_t *options_p = dosync_arg_p->options_p; indexes_t *indexes_p = dosync_arg_p->indexes_p; printf_ddd("Debug3: Committing the file (flags[MODE] == %i)\n", options_p->flags[MODE]); if( (options_p->flags[MODE] == MODE_RSYNCDIRECT) || (options_p->flags[MODE] == MODE_RSYNCSHELL) || (options_p->flags[MODE] == MODE_RSYNCSO) ) g_hash_table_foreach_remove(indexes_p->out_lines_aggr_ht, rsync_aggrout, dosync_arg_p); if(options_p->flags[MODE] != MODE_SO) { fclose(dosync_arg_p->outf); dosync_arg_p->outf = NULL; } if(dosync_arg_p->evcount > 0) { /* if(options_p->flags[PTHREAD]) return sync_exec_thread(options_p, sync_idle_dosync_collectedevents_cleanup, options_p->handlerfpath, options_p->flags[RSYNC]?"rsynclist":"synclist", options_p->label, dosync_arg_p->outf_path, *(dosync_arg_p->outf_path)?dosync_arg_p->outf_path:NULL, NULL); else return sync_exec (options_p, sync_idle_dosync_collectedevents_cleanup, options_p->handlerfpath, options_p->flags[RSYNC]?"rsynclist":"synclist", options_p->label, dosync_arg_p->outf_path, *(dosync_arg_p->outf_path)?dosync_arg_p->outf_path:NULL, NULL);*/ printf_ddd("Debug3: %s [%s] (%p) -> %s [%s]\n", options_p->watchdir, options_p->watchdirwslash, options_p->watchdirwslash, options_p->destdir?options_p->destdir:"", options_p->destdirwslash?options_p->destdirwslash:""); if(options_p->flags[MODE] == MODE_SO) { api_eventinfo_t *ei = dosync_arg_p->api_ei; return so_call_sync(options_p, indexes_p, dosync_arg_p->evcount, ei); } if(options_p->flags[MODE] == MODE_RSYNCSO) return so_call_rsync( options_p, indexes_p, dosync_arg_p->outf_path, *(dosync_arg_p->excf_path) ? dosync_arg_p->excf_path : NULL); if(options_p->flags[MODE] == MODE_RSYNCDIRECT) return SYNC_EXEC(options_p, indexes_p, sync_idle_dosync_collectedevents_cleanup, options_p->handlerfpath, "--inplace", "-aH", "--delete-before", *(dosync_arg_p->excf_path) ? "--exclude-from" : "--include-from", *(dosync_arg_p->excf_path) ? dosync_arg_p->excf_path : dosync_arg_p->outf_path, *(dosync_arg_p->excf_path) ? "--include-from" : "--exclude=*", *(dosync_arg_p->excf_path) ? dosync_arg_p->outf_path : options_p->watchdirwslash, *(dosync_arg_p->excf_path) ? "--exclude=*" : options_p->destdirwslash, *(dosync_arg_p->excf_path) ? options_p->watchdirwslash : NULL, *(dosync_arg_p->excf_path) ? options_p->destdirwslash : NULL, NULL); return SYNC_EXEC(options_p, indexes_p, sync_idle_dosync_collectedevents_cleanup, options_p->handlerfpath, options_p->flags[MODE]==MODE_RSYNCSHELL?"rsynclist":"synclist", options_p->label, dosync_arg_p->outf_path, *(dosync_arg_p->excf_path)?dosync_arg_p->excf_path:NULL, NULL); } return 0; #ifdef DOXYGEN sync_exec(NULL, NULL); sync_exec_thread(NULL, NULL); #endif } 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; options_t *options_p = dosync_arg_p->options_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; printf_ddd("Debug3: sync_idle_dosync_collectedevents_listpush(): \"%s\" with int-flags %p. " "evinfo: seqid_min == %u, seqid_max == %u type_o == %i, type_n == %i\n", fpath, (void *)(unsigned long)evinfo->flags, evinfo->seqid_min, evinfo->seqid_max, evinfo->objtype_old, evinfo->objtype_new ); // so-module case: if(options_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(!( (options_p->flags[MODE] == MODE_RSYNCSHELL) || (options_p->flags[MODE] == MODE_RSYNCDIRECT) || (options_p->flags[MODE] == MODE_RSYNCSO) )) { // non-RSYNC case if(options_p->flags[SYNCLISTSIMPLIFY]) fprintf(outf, "%s\n", fpath); else fprintf(outf, "sync %s %i %s\n", options_p->label, evinfo->evmask, fpath); return; } // RSYNC case if(options_p->rsyncinclimit && (*linescount_p >= options_p->rsyncinclimit)) { int ret; // TODO: optimize this out { char newexc_path[PATH_MAX+1]; if((ret=sync_idle_dosync_collectedevents_uniqfname(options_p, newexc_path, "exclist"))) { printf_e("Error: sync_idle_dosync_collectedevents_listpush(): Cannot get unique file name.\n"); exit(ret); } if((ret=fileutils_copy(dosync_arg_p->excf_path, newexc_path))) { printf_e("Error: sync_idle_dosync_collectedevents_listpush(): Cannot copy file \"%s\" to \"%s\".\n", 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))) { printf_e("Error: sync_idle_dosync_collectedevents_listpush(): Cannot commit list-file \"%s\": %s (errno: %i)\n", dosync_arg_p->outf_path, strerror(ret), ret); exit(ret); // TODO: replace with kill(0, ...); } strcpy(dosync_arg_p->excf_path, newexc_path); // TODO: optimize this out if((ret=sync_idle_dosync_collectedevents_listcreate(dosync_arg_p, "list"))) { printf_e("Error: sync_idle_dosync_collectedevents_listpush(): Cannot create new list-file: %s (errno: %i)\n", strerror(ret), ret); exit(ret); // TODO: replace with kill(0, ...); } outf = dosync_arg_p->outf; } int ret; if((ret=rsync_listpush(indexes_p, fpath, strlen(fpath), evinfo->flags, linescount_p))) { printf_e("Error: sync_idle_dosync_collectedevents_listpush(): Got error from rsync_listpush(). Exit.\n"); exit(ret); } return; } int sync_idle_dosync_collectedevents(options_t *options_p, indexes_t *indexes_p) { printf_ddd("Debug3: sync_idle_dosync_collectedevents()\n"); struct dosync_arg dosync_arg = {0}; dosync_arg.options_p = options_p; dosync_arg.indexes_p = indexes_p; char isrsyncpreferexclude = ( (options_p->flags[MODE] == MODE_RSYNCDIRECT) || (options_p->flags[MODE] == MODE_RSYNCSHELL) || (options_p->flags[MODE] == MODE_RSYNCSO) ) && (!options_p->flags[RSYNCPREFERINCLUDE]); #ifdef PARANOID if(options_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: options_p->synctime = time(NULL) + options_p->syncdelay; printf_ddd("Debug3: Next sync will be not before: %u\n", options_p->synctime); int queue_id=0; while(queue_id < QUEUE_MAX) { int ret; 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, options_p, indexes_p, &dosync_arg); if(ret) { printf_e("Error: Got error while processing queue #%i\n: %s (errno: %i).\n", queue_id, strerror(ret), ret); 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) { printf_ddd("Debug3: sync_idle_dosync_collectedevents(): Summary events' count is zero. Return 0.\n"); return 0; } if(options_p->flags[MODE] == MODE_SO) { //dosync_arg.evcount = g_hash_table_size(indexes_p->fpath2ei_ht); printf_ddd("Debug3: sync_idle_dosync_collectedevents(): There's %i events. Processing.\n", dosync_arg.evcount); dosync_arg.api_ei = (api_eventinfo_t *)xmalloc(dosync_arg.evcount * sizeof(*dosync_arg.api_ei)); } if((options_p->listoutdir != NULL) || (options_p->flags[MODE] == MODE_SO)) { int ret; if(!(options_p->flags[MODE]==MODE_SO)) { *(dosync_arg.excf_path) = 0x00; if(isrsyncpreferexclude) { if((ret=sync_idle_dosync_collectedevents_listcreate(&dosync_arg, "exclist"))) { printf_e("Error: Cannot create list-file: %s (errno: %i)\n", strerror(ret), ret); 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); fclose(dosync_arg.outf); strcpy(dosync_arg.excf_path, dosync_arg.outf_path); // TODO: remove this strcpy() } if((ret=sync_idle_dosync_collectedevents_listcreate(&dosync_arg, "list"))) { printf_e("Error: Cannot create list-file: %s (errno: %i)\n", strerror(ret), ret); return ret; } } #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))) { printf_e("Error: Cannot submit to sync the list \"%s\": %s (errno: %i)\n", dosync_arg.outf_path, strerror(ret), ret); // 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); } return 0; } int apievinfo2rsynclist(indexes_t *indexes_p, FILE *listfile, int n, api_eventinfo_t *apievinfo) { int i; if(listfile == NULL) { printf_e("Error: apievinfo2rsynclist(): listfile == NULL.\n"); return EINVAL; } i=0; while(iout_lines_aggr_ht, rsync_aggrout, &dosync_arg); return 0; } int sync_idle(int notify_d, options_t *options_p, indexes_t *indexes_p) { // Collecting garbage int ret=thread_gc(options_p); if(ret) return ret; // Checking if we can sync if(options_p->flags[STANDBYFILE]) { struct stat st; if(!stat(options_p->standbyfile, &st)) { printf_d("Debug3: sync_idle(): Found standby file. Holding over syncs. Sleeping "XTOSTR(SLEEP_SECONDS)" second.\n"); sleep(SLEEP_SECONDS); return 0; } } // Syncing printf_ddd("Debug3: sync_idle(): calling sync_idle_dosync_collectedevents()\n"); #ifdef CLUSTER_SUPPORT ret = cluster_lock_byindexes(); if(ret) return ret; #endif ret = sync_idle_dosync_collectedevents(options_p, indexes_p); if(ret) return ret; #ifdef CLUSTER_SUPPORT ret = cluster_unlock_all(); if(ret) return ret; #endif return 0; } #ifdef FANOTIFY_SUPPORT int sync_fanotify_loop(int fanotify_d, options_t *options_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) { printf_e("Error: cannot read(%i, &metadata, sizeof(metadata)): %s (errno: %i).\n", fanotify_d, strerror(errno), errno); return errno; } while(FAN_EVENT_OK(metadata, len)) { printf_dd("Debug2: metadata->pid: %i; metadata->fd: %i\n", metadata->pid, metadata->fd); if (metadata->fd != FAN_NOFD) { if (metadata->fd >= 0) { char *fpath = fd2fpath_malloc(metadata->fd); sync_queuesync(fpath_rel, 0, options_p, indexes_p, QUEUE_AUTO); printf_dd("Debug2: Event %i on \"%s\".\n", metadata->mask, fpath); free(fpath); } } close(metadata->fd); metadata = FAN_EVENT_NEXT(metadata, len); } int ret; if((ret=sync_idle(fanotify_d, options_p, indexes_p))) { printf_e("Error: got error while sync_idle(): %s (errno: %i).\n", strerror(ret), ret); return ret; } } return 0; } #endif int sync_inotify_wait(int inotify_d, options_t *options_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_getinfo(); pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); printf_ddd("Debug3: sync_inotify_wait()\n"); fd_set rfds; FD_ZERO(&rfds); FD_SET(inotify_d, &rfds); long queue_id = 0; while(queue_id < QUEUE_MAX) { queueinfo_t *queueinfo = &options_p->_queues[queue_id++]; if(!queueinfo->stime) continue; if(queueinfo->collectdelay == COLLECTDELAY_INSTANT) { printf_ddd("Debug3: sync_inotify_wait(): There're events in instant queue (#%i), don't waiting.\n", queue_id-1); return 0; } int qdelay = queueinfo->stime + queueinfo->collectdelay - tm; printf_ddd("Debug3: sync_inotify_wait(): queue #%i: %i %i %i -> %i\n", queue_id-1, queueinfo->stime, queueinfo->collectdelay, tm, qdelay); if(qdelay < -(long)options_p->syncdelay) qdelay = -(long)options_p->syncdelay; delay = MIN(delay, qdelay); } long synctime_delay = ((long)options_p->synctime) - ((long)tm); synctime_delay = synctime_delay > 0 ? synctime_delay : 0; printf_ddd("Debug3: delay = MAX(%li, %li)\n", delay, synctime_delay); delay = MAX(delay, synctime_delay); delay = delay > 0 ? delay : 0; if(options_p->flags[PTHREAD]) { time_t _thread_nextexpiretime = thread_nextexpiretime(); printf_ddd("Debug3: thread_nextexpiretime == %i\n", _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 printf_ddd("Debug3: thread_expiredelay == %i\n", thread_expiredelay); thread_expiredelay = thread_expiredelay > 0 ? thread_expiredelay : 0; printf_ddd("Debug3: delay = MIN(%li, %li)\n", delay, thread_expiredelay); delay = MIN(delay, thread_expiredelay); } } if((!delay) || (*state_p != STATE_RUNNING)) return 0; if(options_p->flags[EXITONNOEVENTS]) { // zero delay if "--exit-on-no-events" is set tv.tv_sec = 0; tv.tv_usec = 0; } else { printf_ddd("Debug3: sync_inotify_wait(): sleeping for %li second(s).\n", SLEEP_SECONDS); sleep(SLEEP_SECONDS); delay = ((long)delay)>SLEEP_SECONDS ? delay-SLEEP_SECONDS : 0; tv.tv_sec = delay; tv.tv_usec = 0; } pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); if(*state_p != STATE_RUNNING) return 0; printf_ddd("Debug3: sync_inotify_wait(): select with timeout %li secs.\n", tv.tv_sec); int ret = select(inotify_d+1, &rfds, NULL, NULL, &tv); if((ret == -1) && (errno == EINTR)) { errno = 0; ret = 0; } if((options_p->flags[EXITONNOEVENTS]) && (ret == 0)) // if not events and "--exit-on-no-events" is set *state_p = STATE_EXIT; return ret; } void sync_inotify_handle_dosync(gpointer fpath_gp, gpointer evinfo_gp, gpointer arg_gp) { char *fpath_rel = (char *)fpath_gp; eventinfo_t *evinfo = (eventinfo_t *)evinfo_gp; options_t *options_p = ((struct dosync_arg *)arg_gp)->options_p; indexes_t *indexes_p = ((struct dosync_arg *)arg_gp)->indexes_p; sync_queuesync(fpath_rel, evinfo, options_p, indexes_p, QUEUE_AUTO); return; } #define SYNC_INOTIFY_HANDLE_CONTINUE {\ ptr += sizeof(struct inotify_event) + event->len;\ count++;\ continue;\ } int sync_inotify_handle(int inotify_d, options_t *options_p, indexes_t *indexes_p) { static struct timeval tv={0}; 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 = 0; 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) { printf_e("Error: Got error while reading events from inotify with read(): %s (errno: %i).\n", strerror(errno), errno); count = -1; goto l_sync_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) { printf_dd("Debug2: Cleaning up info about watch descriptor %i.\n", event->wd); indexes_remove_bywd(indexes_p, event->wd); SYNC_INOTIFY_HANDLE_CONTINUE; } // Getting path char *fpath = indexes_wd2fpath(indexes_p, event->wd); if(fpath == NULL) { printf_dd("Debug2: Event %p on stale watch (wd: %i).\n", (void *)(long)event->mask, event->wd); SYNC_INOTIFY_HANDLE_CONTINUE; } printf_dd("Debug2: Event %p on \"%s\" (wd: %i; fpath: \"%s\").\n", (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 stat64 lstat; mode_t st_mode; size_t st_size; if(lstat64(path_full, &lstat)) { printf_dd("Debug2: Cannot lstat(\"%s\", lstat): %s (errno: %i). Seems, that the object disappeared.\n", path_full, strerror(errno), errno); if(event->mask & IN_ISDIR) st_mode = S_IFDIR; else st_mode = S_IFREG; st_size = 0; } else { st_mode = lstat.st_mode; st_size = lstat.st_size; } // Checking by filter rules path_rel = sync_path_abs2rel(options_p, path_full, -1, &path_rel_len, path_rel); ruleaction_t perm = rules_getperm(path_rel, st_mode, options_p->rules, RA_WALK|RA_MONITOR); if(!(perm&(RA_MONITOR|RA_WALK))) { SYNC_INOTIFY_HANDLE_CONTINUE; } // Handling different cases if(event->mask & IN_ISDIR) { if(event->mask & (IN_CREATE|IN_MOVED_TO)) { // Appeared // If new dir is created int ret; if(perm & RA_WALK) { ret = sync_mark_walk(inotify_d, options_p, path_full, indexes_p); if(ret) { printf_d("Debug: Seems, that directory \"%s\" disappeared, while trying to mark it.\n", path_full); SYNC_INOTIFY_HANDLE_CONTINUE; } ret = sync_initialsync(path_full, options_p, indexes_p, INITSYNC_SUBDIR); if(ret) { printf_e("Error: Got error from sync_initialsync(): %s (errno %i)\n", strerror(ret), ret); errno = ret; count=-1; goto l_sync_inotify_handle_end; } } SYNC_INOTIFY_HANDLE_CONTINUE; } else if(event->mask & (IN_DELETE_SELF|IN_DELETE|IN_MOVED_FROM)) { // Disappered printf_dd("Debug2: Disappeared \"%s\".\n", path_full); } } if(!(perm&RA_WALK)) { SYNC_INOTIFY_HANDLE_CONTINUE; } // Locally queueing the event int isnew = 0; eventinfo_t *evinfo = indexes_fpath2ei(indexes_p, path_rel); 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 = (event->mask & IN_CREATE) ? EOT_DOESNTEXIST : (event->mask & IN_ISDIR) ? EOT_DIR : EOT_FILE; isnew++; printf_ddd("Debug3: sync_inotify_handle(): new event: fsize == %i; wd == %i\n", evinfo->fsize, evinfo->wd); } else { evinfo->seqid_max = sync_seqid(); } evinfo->evmask |= event->mask; evinfo->objtype_new = (event->mask & (IN_DELETE_SELF|IN_DELETE|IN_MOVED_FROM)) ? EOT_DOESNTEXIST : (event->mask & IN_ISDIR) ? EOT_DIR : EOT_FILE; printf_dd("Debug2: sync_inotify_handle(): path_rel == \"%s\"; evinfo->objtype_old == %i; evinfo->objtype_new == %i; " "evinfo->seqid_min == %u; evinfo->seqid_max == %u\n", path_rel, evinfo->objtype_old, evinfo->objtype_new, evinfo->seqid_min, evinfo->seqid_max ); if(isnew) indexes_fpath2ei_add(indexes_p, strdup(path_rel), evinfo); SYNC_INOTIFY_HANDLE_CONTINUE; } // Globally queueing captured events struct dosync_arg dosync_arg; dosync_arg.options_p = options_p; dosync_arg.indexes_p = indexes_p; printf_ddd("Debug3: sync_inotify_handle(): collected %i events per this time.\n", g_hash_table_size(indexes_p->fpath2ei_ht)); g_hash_table_foreach(indexes_p->fpath2ei_ht, sync_inotify_handle_dosync, &dosync_arg); g_hash_table_remove_all(indexes_p->fpath2ei_ht); } l_sync_inotify_handle_end: if(path_full != NULL) free(path_full); if(path_rel != NULL) free(path_rel); return count; } #define SYNC_INOTIFY_LOOP_IDLE {\ int ret;\ if((ret=sync_idle(inotify_d, options_p, indexes_p))) {\ printf_e("Error: got error while sync_idle(): %s (errno: %i).\n", strerror(ret), ret);\ return ret;\ }\ } #define SYNC_INOTIFY_LOOP_CONTINUE_UNLOCK {\ pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]);\ pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]);\ continue;\ } int sync_inotify_loop(int inotify_d, options_t *options_p, indexes_t *indexes_p) { int state = options_p->flags[SKIPINITSYNC] ? STATE_RUNNING : STATE_INITSYNC; int ret; state_p = &state; while(state != STATE_EXIT) { int events; threadsinfo_t *threadsinfo_p = thread_getinfo(); pthread_mutex_lock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); printf_ddd("Debug3: sync_inotify_loop(): current state is %i\n", state); events = 0; switch(state) { case STATE_PTHREAD_GC: main_status_update(options_p, state); if(thread_gc(options_p)) { state=STATE_EXIT; break; } state = STATE_RUNNING; SYNC_INOTIFY_LOOP_CONTINUE_UNLOCK; case STATE_INITSYNC: main_status_update(options_p, state); pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); ret = sync_initialsync(options_p->watchdir, options_p, indexes_p, INITSYNC_FULL); if(ret) return ret; if(options_p->flags[ONLYINITSYNC]) { SYNC_INOTIFY_LOOP_IDLE; state = STATE_EXIT; return ret; } state = STATE_RUNNING; continue; case STATE_RUNNING: events = sync_inotify_wait(inotify_d, options_p, indexes_p); if(state != STATE_RUNNING) SYNC_INOTIFY_LOOP_CONTINUE_UNLOCK; break; case STATE_REHASH: main_status_update(options_p, state); printf_d("Debug: sync_inotify_loop(): rehashing.\n"); main_rehash(options_p); state = STATE_RUNNING; SYNC_INOTIFY_LOOP_CONTINUE_UNLOCK; case STATE_TERM: main_status_update(options_p, state); state = STATE_EXIT; case STATE_EXIT: main_status_update(options_p, state); SYNC_INOTIFY_LOOP_CONTINUE_UNLOCK; } pthread_cond_broadcast(&threadsinfo_p->cond[PTHREAD_MUTEX_STATE]); pthread_mutex_unlock(&threadsinfo_p->mutex[PTHREAD_MUTEX_STATE]); if(events == 0) { printf_dd("Debug2: sync_inotify_wait(%i, options_p, indexes_p) timed-out.\n", inotify_d); SYNC_INOTIFY_LOOP_IDLE; continue; // Timeout } if(events < 0) { printf_e("Error: Got error while waiting for event from inotify with select(): %s (errno: %i).\n", strerror(errno), errno); return errno; } int count=sync_inotify_handle(inotify_d, options_p, indexes_p); if(count <= 0) { printf_e("Error: Cannot handle with inotify events: %s (errno: %i).\n", strerror(errno), errno); return errno; } main_status_update(options_p, state); if(options_p->flags[EXITONNOEVENTS]) // clsync exits on no events, so sync_idle() is never called. We have to force the calling of it. SYNC_INOTIFY_LOOP_IDLE; } SYNC_INOTIFY_LOOP_IDLE; return exitcode; #ifdef DOXYGEN sync_idle(0, NULL, NULL); #endif } int sync_notify_loop(int notify_d, options_t *options_p, indexes_t *indexes_p) { switch(options_p->notifyengine) { #ifdef FANOTIFY_SUPPORT case NE_FANOTIFY: return sync_fanotify_loop(notify_d, options_p, indexes_p); #endif case NE_INOTIFY: return sync_inotify_loop (notify_d, options_p, indexes_p); } printf_e("Error: unknown notify-engine: %i\n", options_p->notifyengine); errno = EINVAL; return -1; } void sync_sig_int(int signal) { printf_dd("Debug2: sync_sig_int(%i): Thread %p\n", signal, pthread_self()); return; } int sync_switch_state(pthread_t pthread_parent, int newstate) { if(state_p == NULL) { printf_ddd("Debug3: sync_switch_state(%p, %i), but state_p == NULL\n", pthread_parent, newstate); return 0; } printf_ddd("Debug3: sync_switch_state(%p, %i)\n", pthread_parent, newstate); // Getting mutexes threadsinfo_t *threadsinfo_p = thread_getinfo(); 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_cond_t *pthread_cond_state = &threadsinfo_p->cond [PTHREAD_MUTEX_STATE]; // Locking all necessary mutexes if(pthread_mutex_trylock(pthread_mutex_state) == EBUSY) { while(1) { struct timespec time_timeout; clock_gettime(CLOCK_REALTIME, &time_timeout); time_timeout.tv_sec++; // time_timeout.tv_sec = now.tv_sec; printf_ddd("Debug3: sync_switch_state(): pthread_cond_timedwait() until %li.%li\n", time_timeout.tv_sec, time_timeout.tv_nsec); if(pthread_cond_timedwait(pthread_cond_state, pthread_mutex_state, &time_timeout) != ETIMEDOUT) break; printf_ddd("Debug3: sending signal to interrupt blocking operations like select()-s and so on\n"); pthread_kill(pthread_parent, SIGUSR_BLOPINT); #ifdef VERYPARANOID int i=0; if(++i > KILL_TIMEOUT) { printf_e("Error: sync_switch_state(): Seems we got a deadlock."); return EDEADLK; } #endif } } // Changing the state *state_p = newstate; #ifdef VERYPARANOID pthread_kill(pthread_parent, SIGUSR_BLOPINT); #endif // Unlocking mutexes printf_ddd("Debug3: sync_switch_state(): pthread_mutex_unlock(). New state is %i.\n", *state_p); pthread_cond_broadcast(pthread_cond_state); pthread_mutex_unlock(pthread_mutex_state); return 0; l_sync_parent_interrupt_end: *state_p = newstate; pthread_kill(pthread_parent, SIGUSR_BLOPINT); return 0; } int *sync_sighandler_exitcode_p = NULL; int sync_sighandler(sighandler_arg_t *sighandler_arg_p) { int signal, ret; options_t *options_p = sighandler_arg_p->options_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; while(1) { printf_ddd("Debug3: sync_sighandler(): waiting for signal\n"); ret = sigwait(sigset_p, &signal); if(state_p == NULL) { switch(signal) { case SIGALRM: *exitcode_p = ETIME; case SIGTERM: case SIGINT: // TODO: remove the exit() from here. Main thread should exit itself exit(*exitcode_p); break; default: printf_e("Warning: Got signal %i, but the main loop is not started, yet. Ignoring the signal.\n", signal); break; } continue; } printf_ddd("Debug3: sync_sighandler(): got signal %i. *state_p == %i.\n", signal, *state_p); if(ret) { // TODO: handle an error here } switch(signal) { case SIGALRM: *exitcode_p = ETIME; case SIGTERM: case SIGINT: sync_switch_state(pthread_parent, STATE_TERM); // bugfix of https://github.com/xaionaro/clsync/issues/44 while(options_p->children) { // Killing children if non-pthread mode or/and (mode=="so" or mode=="rsyncso") pid_t child_pid = options_p->child_pid[--options_p->children]; if(waitpid(child_pid, NULL, WNOHANG)>=0) { printf_ddd("Debug3: sync_sighandler(): Sending signal %u to child process with pid %u.\n", signal, child_pid); kill(child_pid, signal); sleep(1); // TODO: replace this sleep() with something to do not sleep if process already died } else continue; if(waitpid(child_pid, NULL, WNOHANG)>=0) { printf_ddd("Debug3: sync_sighandler(): Sending signal SIGQUIT to child process with pid %u.\n", child_pid); kill(child_pid, SIGQUIT); sleep(1); // TODO: replace this sleep() with something to do not sleep if process already died } else continue; if(waitpid(child_pid, NULL, WNOHANG)>=0) { printf_ddd("Debug3: sync_sighandler(): Sending signal SIGKILL to child process with pid %u.\n", child_pid); kill(child_pid, SIGKILL); } } break; case SIGHUP: sync_switch_state(pthread_parent, STATE_REHASH); break; case SIGUSR_PTHREAD_GC: sync_switch_state(pthread_parent, STATE_PTHREAD_GC); break; case SIGUSR_INITSYNC: sync_switch_state(pthread_parent, STATE_INITSYNC); break; default: printf_e("Error: Unknown signal: %i. Exit.\n", signal); sync_switch_state(pthread_parent, STATE_TERM); break; } if((*state_p == STATE_TERM) || (*state_p == STATE_EXIT)) { break; } } printf_ddd("Debug3: sync_sighandler(): signal handler closed.\n"); return 0; } int sync_term(int exitcode) { *sync_sighandler_exitcode_p = exitcode; return pthread_kill(pthread_sighandler, SIGTERM); } int sync_run(options_t *options_p) { int ret, i; sighandler_arg_t sighandler_arg = {0}; // Creating signal handler thread sigset_t sigset_sighandler; sigemptyset(&sigset_sighandler); sigaddset(&sigset_sighandler, SIGALRM); sigaddset(&sigset_sighandler, SIGHUP); sigaddset(&sigset_sighandler, SIGTERM); sigaddset(&sigset_sighandler, SIGINT); sigaddset(&sigset_sighandler, SIGUSR_PTHREAD_GC); sigaddset(&sigset_sighandler, SIGUSR_INITSYNC); ret = pthread_sigmask(SIG_BLOCK, &sigset_sighandler, NULL); if(ret) return ret; sighandler_arg.options_p = options_p; // sighandler_arg.indexes_p = &indexes; 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); // Creating hash tables indexes_t indexes = {NULL}; 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); i=0; while(iflags[MODE] == MODE_SO || options_p->flags[MODE] == MODE_RSYNCSO) { /* security checks before dlopen */ struct stat so_stat; if (stat(options_p->handlerfpath, &so_stat) == -1) { printf_e("Can't stat shared object file \"%s\": %s\n", options_p->handlerfpath, strerror(errno)); return errno; } // allow normal files only (stat will follow symlinks) if (!S_ISREG(so_stat.st_mode)) { printf_e("Shared object \"%s\" must be a regular file (or symlink to a regular file).\n", options_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) { printf_e("Can't stat clsync binary file \"%s\": %s\n", cl_str, strerror(errno)); } if (ret == -1 || so_stat.st_uid != cl_stat.st_uid) { printf_e("Wrong owner for shared object \"%s\": %i\n" "Only root, clsync file owner and user started the program are allowed.\n", options_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)) { printf_e("Wrong shared object \"%s\" permissions: %#lo\n" "Special bits, group and world writable are not allowed.\n", options_p->handlerfpath, so_stat.st_mode & 07777); return EPERM; } // dlopen() void *synchandler_handle = dlopen(options_p->handlerfpath, RTLD_NOW|RTLD_LOCAL); if(synchandler_handle == NULL) { printf_e("Error: sync_run(): Cannot load shared object file \"%s\": %s\n", options_p->handlerfpath, dlerror()); return -1; } // resolving init, sync and deinit functions' handlers options_p->handler_handle = synchandler_handle; options_p->handler_funct.init = (api_funct_init) dlsym(options_p->handler_handle, API_PREFIX"init"); if(options_p->flags[MODE] == MODE_RSYNCSO) { options_p->handler_funct.rsync = (api_funct_rsync)dlsym(options_p->handler_handle, API_PREFIX"rsync"); if(options_p->handler_funct.rsync == NULL) { char *dlerror_str = dlerror(); printf_e("Error: sync_run(): Cannot resolve symbol "API_PREFIX"rsync in shared object \"%s\": %s\n", options_p->handlerfpath, dlerror_str != NULL ? dlerror_str : "No error description returned."); } } else { options_p->handler_funct.sync = (api_funct_sync)dlsym(options_p->handler_handle, API_PREFIX"sync"); if(options_p->handler_funct.sync == NULL) { char *dlerror_str = dlerror(); printf_e("Error: sync_run(): Cannot resolve symbol "API_PREFIX"sync in shared object \"%s\": %s\n", options_p->handlerfpath, dlerror_str != NULL ? dlerror_str : "No error description returned."); } } options_p->handler_funct.deinit = (api_funct_deinit)dlsym(options_p->handler_handle, API_PREFIX"deinit"); // running init function if(options_p->handler_funct.init != NULL) if((ret = options_p->handler_funct.init(options_p, &indexes))) { printf_e("Error: sync_run(): Cannot init sync-handler module: %s (errno: %i).\n", strerror(ret), ret); return ret; } } #ifdef CLUSTER_SUPPORT // Initializing cluster subsystem if(options_p->cluster_iface != NULL) { ret = cluster_init(options_p, &indexes); if(ret) { printf_e("Error: Cannot initialize cluster subsystem: %s (errno %i).\n", strerror(ret), ret); cluster_deinit(); return ret; } } #endif // Initializing rand-generator if it's required if(options_p->listoutdir) srand(time(NULL)); int notify_d=0; if(!options_p->flags[ONLYINITSYNC]) { // Initializing FS monitor kernel subsystem in this userspace application notify_d = sync_notify_init(options_p); if(notify_d == -1) return errno; // Marking file tree for FS monitor ret = sync_mark_walk(notify_d, options_p, options_p->watchdir, &indexes); if(ret) return ret; } // "Infinite" loop of processling the events ret = sync_notify_loop(notify_d, options_p, &indexes); if(ret) return ret; // TODO: Do cleanup of watching points pthread_kill(pthread_sighandler, SIGTERM); pthread_join(pthread_sighandler, NULL); // Killing children thread_cleanup(options_p); // Closing sockets and files printf_ddd("sync_run(): Closing notify_d\n"); close(notify_d); // Closing shared libraries if(options_p->flags[MODE] == MODE_SO) { int _ret; if(options_p->handler_funct.deinit != NULL) if((_ret = options_p->handler_funct.deinit())) { printf_e("Error: sync_run(): Cannot deinit sync-handler module: %s (errno: %i).\n", strerror(ret), ret); if(!ret) ret = _ret; } if(dlclose(options_p->handler_handle)) { printf_e("Error: sync_run(): Cannot unload shared object file \"%s\": %s\n", options_p->handlerfpath, dlerror()); if(!ret) ret = -1; } } // Cleaning up run-time routines rsync_escape_cleanup(); // Removing hash-tables printf_ddd("sync_run(): Closing hash tables\n"); 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); i=0; while(icluster_iface != NULL) { int _ret; _ret = cluster_deinit(); if(_ret) { printf_e("Error: Cannot deinitialize cluster subsystem: %s (errno: %i).\n", strerror(_ret), _ret); ret = _ret; } } #endif #ifdef VERYPARANOID // One second for another threads sleep(1); #endif if(options_p->flags[EXITHOOK]) { char *argv[] = { options_p->exithookfile, options_p->label, NULL}; exec_argv(argv, NULL DEBUGV(, 0)); } return ret; } clsync-0.2.1/sync.h000066400000000000000000000015661222700035500141360ustar00rootroot00000000000000/* 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 sync_run(struct options *options); extern int sync_term(int exitcode);