pax_global_header00006660000000000000000000000064133374673140014525gustar00rootroot0000000000000052 comment=ce67c5591c0e0055147f52f3caab966813c7bed7 csync2-2.0-22-gce67c55/000077500000000000000000000000001333746731400142365ustar00rootroot00000000000000csync2-2.0-22-gce67c55/AUTHORS000066400000000000000000000010311333746731400153010ustar00rootroot00000000000000Copyright (C) 2004 - 2013 LINBIT Information Technologies GmbH http://www.linbit.com Original csync2 author, no longer involved with the project: 2004, 2005 Clifford Wolf Most development 1.34 to 2.0 2010 Johannes Thoma Coordination, fixes and maintenance: Lars Ellenberg Major contributions: 2010 Dennis Schafroth 2008 Art -kwaak- van Breemen And others; for details see the git log. csync2-2.0-22-gce67c55/COPYING000066400000000000000000000432541333746731400153010ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. csync2-2.0-22-gce67c55/ChangeLog000066400000000000000000000002131333746731400160040ustar00rootroot00000000000000Please see git for a complete changelog: http://git.linbit.com/csync2.git/ git clone git://git.linbit.com/csync2.git cd csync2 && git log csync2-2.0-22-gce67c55/INSTALL000066400000000000000000000001401333746731400152620ustar00rootroot00000000000000See the README file for install instructions. This file is only here because automake wants it. csync2-2.0-22-gce67c55/Makefile.am000066400000000000000000000101161333746731400162710ustar00rootroot00000000000000# csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # sbin_PROGRAMS = csync2 sbin_SCRIPTS = csync2-compare man_MANS = csync2.1 csync2_SOURCES = action.c cfgfile_parser.y cfgfile_scanner.l check.c \ checktxt.c csync2.c daemon.c db.c error.c getrealfn.c \ groups.c rsync.c update.c urlencode.c conn.c prefixsubst.c \ db_api.c db_sqlite.c db_sqlite2.c db_mysql.c db_postgres.c \ csync2.h db_api.h db_mysql.h db_postgres.h db_sqlite.h db_sqlite2.h dl.h \ csync2-compare \ csync2.1 doc/csync2_paper.tex \ csync2.spec EXTRA_DIST = csync2.cfg csync2.xinetd AM_YFLAGS = -d BUILT_SOURCES = cfgfile_parser.h LIBS += -ldl CLEANFILES = cfgfile_parser.c cfgfile_parser.h cfgfile_scanner.c \ private_librsync private_libsqlite config.log DISTCLEANFILES = config.status config.h .deps/*.Po stamp-h1 Makefile Makefile.in configure docfiles = ChangeLog README AUTHORS if HAVE_PDFLATEX MANNAME = doc/csync2_paper MANTEXSRC = $(MANNAME).tex MANAUX = $(MANNAME).aux MANPDF = $(MANNAME).pdf docfiles += $(MANPDF) CLEANFILES += $(MANPDF) $(MANAUX) $(MANNAME).log $(MANNAME).idx $(MANNAME).out $(MANNAME).toc $(MANPDF): $(MANTEXSRC) $(PDFLATEX) -interaction=batchmode -output-directory doc $< >/dev/null $(PDFLATEX) -interaction=batchmode -output-directory doc $< >/dev/null endif doc_DATA = $(docfiles) dist_doc_DATA = $(docfiles) dist-clean-local: rm -rf autom4te.cache AM_CFLAGS=$(LIBGNUTLS_CFLAGS) -Wall AM_LDFLAGS=$(LIBGNUTLS_LIBS) LIBS += $(LIBGNUTLS_LIBS) if PRIVATE_LIBRSYNC BUILT_SOURCES += private_librsync AM_CFLAGS += -I$(shell test -f librsync.dir && cat librsync.dir || echo ==librsync==) AM_LDFLAGS += -L$(shell test -f librsync.dir && cat librsync.dir || echo ==librsync==) LIBS += -lprivatersync endif AM_CPPFLAGS = -D'DBDIR="$(localstatedir)/lib/csync2"' AM_CPPFLAGS += -D'ETCDIR="$(sysconfdir)"' install-data-local: $(mkinstalldirs) $(DESTDIR)$(sysconfdir) $(mkinstalldirs) $(DESTDIR)$(localstatedir)/lib/csync2 test -e $(DESTDIR)$(sysconfdir)/csync2.cfg || \ $(INSTALL_DATA) $(srcdir)/csync2.cfg $(DESTDIR)$(sysconfdir)/csync2.cfg cert: $(mkinstalldirs) $(DESTDIR)$(sysconfdir) openssl genrsa -out $(DESTDIR)$(sysconfdir)/csync2_ssl_key.pem 1024 yes '' | openssl req -new -key $(DESTDIR)$(sysconfdir)/csync2_ssl_key.pem \ -out $(DESTDIR)$(sysconfdir)/csync2_ssl_cert.csr openssl x509 -req -days 600 -in $(DESTDIR)$(sysconfdir)/csync2_ssl_cert.csr \ -signkey $(DESTDIR)$(sysconfdir)/csync2_ssl_key.pem \ -out $(DESTDIR)$(sysconfdir)/csync2_ssl_cert.pem rm $(DESTDIR)$(sysconfdir)/csync2_ssl_cert.csr ## hack for building private librsync and private libsqlite ## private_librsync: tar xvzf $(librsync_source_file) | cut -f1 -d/ | sed '1 p; d;' > librsync.dir test -s librsync.dir && cd $$( cat librsync.dir ) && ./configure --enable-static --disable-shared make -C $$( cat librsync.dir ) cp $$( cat librsync.dir )/.libs/librsync.a $$( cat librsync.dir )/libprivatersync.a touch private_librsync private_libsqlite: tar xvzf $(libsqlite_source_file) | cut -f1 -d/ | sed '1 p; d;' > libsqlite.dir test -s libsqlite.dir && cd $$( cat libsqlite.dir ) && ./configure --enable-static --disable-shared make -C $$( cat libsqlite.dir ) cp $$( cat libsqlite.dir )/.libs/libsqlite.a $$( cat libsqlite.dir )/libprivatesqlite.a touch private_libsqlite csync2-2.0-22-gce67c55/NEWS000066400000000000000000000001261333746731400147340ustar00rootroot00000000000000See the ChangeLog file for changes. This file is only here because autmoake wants it. csync2-2.0-22-gce67c55/README000066400000000000000000000044711333746731400151240ustar00rootroot00000000000000 About csync2 ============ Csync2 is a cluster synchronization tool. It can be used to keep files on multiple hosts in a cluster in sync. Csync2 can handle complex setups with much more than just 2 hosts, handle file deletions and can detect conflicts. It is expedient for HA-clusters, HPC-clusters, COWs and server farms. If you are looking for a tool to sync your laptop with your workstation, you better have a look at Unison (http://www.cis.upenn.edu/~bcpierce/unison/) too. See http://oss.linbit.com/ for more information on csync2. The csync2 git tree can be found at http://git.linbit.com/csync2.git/. Copyright ========= csync2 - cluster synchronization tool, 2nd generation Copyright 2004 - 2015 LINBIT Information Technologies GmbH http://www.linbit.com; see also AUTHORS This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Documentation ============= The latest version of the csync2 documentation can be found online at http://oss.linbit.com/csync2/paper.pdf You should definitely read the documentation before trying to setup csync2. The TeX source of the paper (as well as some slides for csync2 presentations) can be found at http://git.linbit.com/csync2.git/doc/csync2_paper.tex The csync2 releases also have a copy of the 'paper.pdf' file (and the TeX source) bundled in the csync2 source tarball. More Documentation ================== Eric Liang wrote a nice step-by-step introductions for using csync2 on redhat machines: http://zhenhuiliang.blogspot.com/2006/04/csync2-is-so-cool.html Mailing List ============ There is a csync2 mailing list: http://lists.linbit.com/mailman/listinfo/csync2 It is recommended to subscribe to this list if you are using csync2 in production environments. csync2-2.0-22-gce67c55/action.c000066400000000000000000000100231333746731400156530ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include #include void csync_schedule_commands(const char *filename, int islocal) { const struct csync_group *g = NULL; const struct csync_group_action *a = NULL; const struct csync_group_action_pattern *p = NULL; const struct csync_group_action_command *c = NULL; while ( (g=csync_find_next(g, filename)) ) { for (a=g->action; a; a=a->next) { if ( !islocal && a->do_local_only ) continue; if ( islocal && !a->do_local ) continue; if (!a->pattern) goto found_matching_pattern; for (p=a->pattern; p; p=p->next) { int fnm_pathname = p->star_matches_slashes ? 0 : FNM_PATHNAME; if ( !fnmatch(p->pattern, filename, FNM_LEADING_DIR|fnm_pathname) ) goto found_matching_pattern; } continue; found_matching_pattern: for (c=a->command; c; c=c->next) SQL("Add action to database", "INSERT INTO action (filename, command, logfile) " "VALUES ('%s', '%s', '%s')", url_encode(filename), url_encode(c->command), url_encode(a->logfile)); } } } void csync_run_single_command(const char *command, const char *logfile) { char *command_clr = strdup(url_decode(command)); char *logfile_clr = strdup(url_decode(logfile)); char *real_command, *mark; struct textlist *tl = 0, *t; pid_t pid; SQL_BEGIN("Checking for removed files", "SELECT filename from action WHERE command = '%s' " "and logfile = '%s'", command, logfile) { textlist_add(&tl, SQL_V(0), 0); } SQL_END; mark = strstr(command_clr, "%%"); if ( !mark ) { real_command = strdup(command_clr); } else { int len = strlen(command_clr) + 10; char *pos; for (t = tl; t != 0; t = t->next) len += strlen(t->value) + 1; pos = real_command = malloc(len); memcpy(real_command, command_clr, mark-command_clr); real_command[mark-command_clr] = 0; for (t = tl; t != 0; t = t->next) { pos += strlen(pos); if ( t != tl ) *(pos++) = ' '; strcpy(pos, t->value); } pos += strlen(pos); strcpy(pos, mark+2); assert(strlen(real_command)+1 < len); } csync_debug(1, "Running '%s' ...\n", real_command); pid = fork(); if ( !pid ) { close(0); close(1); close(2); /* 0 */ open("/dev/null", O_RDONLY); /* 1 */ open(logfile_clr, O_WRONLY|O_CREAT|O_APPEND, 0666); /* 2 */ open(logfile_clr, O_WRONLY|O_CREAT|O_APPEND, 0666); execl("/bin/sh", "sh", "-c", real_command, NULL); _exit(127); } if ( waitpid(pid, 0, 0) < 0 ) csync_fatal("ERROR: Waitpid returned error %s.\n", strerror(errno)); for (t = tl; t != 0; t = t->next) SQL("Remove action entry", "DELETE FROM action WHERE command = '%s' " "and logfile = '%s' and filename = '%s'", command, logfile, t->value); textlist_free(tl); } void csync_run_commands() { struct textlist *tl = 0, *t; SQL_BEGIN("Checking for sceduled commands", "SELECT command, logfile FROM action GROUP BY command, logfile") { textlist_add2(&tl, SQL_V(0), SQL_V(1), 0); } SQL_END; for (t = tl; t != 0; t = t->next) csync_run_single_command(t->value, t->value2); textlist_free(tl); } csync2-2.0-22-gce67c55/autogen.sh000077500000000000000000000027661333746731400162520ustar00rootroot00000000000000#!/bin/bash -x # # csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA aclocal autoheader automake --add-missing --copy autoconf if [ "$1" = clean ]; then ./configure && make distclean rm -rf librsync[.-]* libsqlite.* sqlite-* rm -rf configure Makefile.in depcomp stamp-h.in rm -rf mkinstalldirs config.h.in autom4te.cache rm -rf missing aclocal.m4 install-sh *~ rm -rf config.guess config.sub rm -rf cygwin/librsync-0.9.7.tar.gz rm -rf cygwin/sqlite-2.8.16.tar.gz else ./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc echo "" echo "Configured as" echo "./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc" echo "" echo "reconfigure, if you want it different" fi csync2-2.0-22-gce67c55/cfgfile_parser.y000066400000000000000000000317531333746731400174140ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ %{ #include "csync2.h" #include #include #include #include #include struct csync_group *csync_group = 0; struct csync_prefix *csync_prefix = 0; struct csync_nossl *csync_nossl = 0; int csync_ignore_uid = 0; int csync_ignore_gid = 0; int csync_ignore_mod = 0; unsigned csync_lock_timeout = 12; char *csync_tempdir = NULL; #ifdef __CYGWIN__ int csync_lowercyg_disable = 0; int csync_lowercyg_used = 0; #endif extern void yyerror(char* text); extern int yylex(); extern int yylineno; void yyerror(char *text) { csync_fatal("Near line %d: %s\n", yylineno, text); } static void new_group(char *name) { int static autonum = 1; int rc = 0; struct csync_group *t = calloc(sizeof(struct csync_group), 1); if (name == 0) rc = asprintf(&name, "group_%d", autonum++); if (!t || rc == -1) csync_fatal("Out of memory while parsing config file.\n"); t->next = csync_group; t->auto_method = -1; t->gname = name; t->backup_generations = 3; csync_group = t; } static void add_host(char *hostname, char *peername, int slave) { int i; for (i=0; hostname[i]; i++) hostname[i] = tolower(hostname[i]); for (i=0; peername[i]; i++) peername[i] = tolower(peername[i]); if ( strcmp(hostname, myhostname) == 0 ) { csync_group->local_slave = slave; csync_group->myname = peername; free(hostname); } else { struct csync_group_host *t = calloc(sizeof(struct csync_group_host), 1); t->hostname = peername; t->on_left_side = !csync_group->myname; t->slave = slave; t->next = csync_group->host; csync_group->host = t; free(hostname); } } static void add_patt(int patterntype, char *pattern) { struct csync_group_pattern *t = calloc(sizeof(struct csync_group_pattern), 1); int i; #if __CYGWIN__ if (isalpha(pattern[0]) && pattern[1] == ':' && (pattern[2] == '/' || pattern[2] == '\\')) { char *new_pattern, *p; asprintf(&new_pattern, "/cygdrive/%c/%s", tolower(pattern[0]), pattern+3); for (p = new_pattern; *p; p++) if (*p == '\\') *p = '/'; free(pattern); pattern = new_pattern; } #endif /* strip trailing slashes from pattern */ for (i=strlen(pattern)-1; i>0; i--) if (pattern[i] == '/') pattern[i] = 0; else break; /* if you use ** at least once anywhere in the pattern, * _all_ stars in the pattern, even single ones, * will match slashes. */ t->star_matches_slashes = !!strstr(pattern, "**"); t->isinclude = patterntype >= 1; t->iscompare = patterntype >= 2; t->pattern = pattern; t->next = csync_group->pattern; csync_group->pattern = t; } static void set_key(char *keyfilename) { FILE *keyfile; char *fname = keyfilename; char line[1024]; int i; if ( csync_group->key ) csync_fatal("Config error: a group might only have one key.\n"); if (fname && fname[0] && fname[0] != '/') ASPRINTF(&fname, "%s/%s", systemdir, keyfilename); if ( (keyfile = fopen(fname, "r")) == 0 || fgets(line, 1024, keyfile) == 0 ) csync_fatal("Config error: Can't read keyfile %s.\n", fname); for (i=0; line[i]; i++) { if (line[i] == '\n') { line[i]=0; break; } if ( !(line[i] >= 'A' && line[i] <= 'Z') && !(line[i] >= 'a' && line[i] <= 'z') && !(line[i] >= '0' && line[i] <= '9') && line[i] != '.' && line[i] != '_' ) csync_fatal("Unallowed character '%c' in key file %s.\n", line[i], fname); } if ( strlen(line) < 32 ) csync_fatal("Config error: Key in file %s is too short.\n", fname); csync_group->key = strdup(line); if (fname != keyfilename) free(fname); free(keyfilename); fclose(keyfile); } static void set_auto(char *auto_method) { int method_id = -1; if (csync_group->auto_method >= 0) csync_fatal("Config error: a group might only have one auto-setting.\n"); if (!strcmp(auto_method, "none")) method_id = CSYNC_AUTO_METHOD_NONE; if (!strcmp(auto_method, "first")) method_id = CSYNC_AUTO_METHOD_FIRST; if (!strcmp(auto_method, "younger")) method_id = CSYNC_AUTO_METHOD_YOUNGER; if (!strcmp(auto_method, "older")) method_id = CSYNC_AUTO_METHOD_OLDER; if (!strcmp(auto_method, "bigger")) method_id = CSYNC_AUTO_METHOD_BIGGER; if (!strcmp(auto_method, "smaller")) method_id = CSYNC_AUTO_METHOD_SMALLER; if (!strcmp(auto_method, "left")) method_id = CSYNC_AUTO_METHOD_LEFT; if (!strcmp(auto_method, "right")) method_id = CSYNC_AUTO_METHOD_RIGHT; if (method_id < 0) csync_fatal("Config error: Unknown auto-setting '%s' (use " "'none', 'younger', 'older', 'bigger', 'smaller', " "'left' or 'right').\n", auto_method); csync_group->auto_method = method_id; free(auto_method); } static void set_bak_dir(char *dir) { csync_group->backup_directory = dir; } static void set_bak_gen(char *gen) { csync_group->backup_generations = atoi(gen); free(gen); } static void check_group() { if ( ! csync_group->key ) csync_fatal("Config error: every group must have a key.\n"); if ( csync_group->auto_method < 0 ) csync_group->auto_method = CSYNC_AUTO_METHOD_NONE; /* re-order hosts and pattern */ { struct csync_group_host *t = csync_group->host; csync_group->host = 0; while ( t ) { struct csync_group_host *next = t->next; t->next = csync_group->host; csync_group->host = t; t = next; } } { struct csync_group_pattern *t = csync_group->pattern; csync_group->pattern = 0; while ( t ) { struct csync_group_pattern *next = t->next; t->next = csync_group->pattern; csync_group->pattern = t; t = next; } } if (active_peerlist) { struct csync_group_host *h; int i=0, thisplen; while (active_peerlist[i]) { thisplen = strcspn(active_peerlist + i, ","); for (h=csync_group->host; h; h=h->next) if (strlen(h->hostname) == thisplen && !strncmp(active_peerlist + i, h->hostname, thisplen)) goto foundactivepeers; i += thisplen; while (active_peerlist[i] == ',') i++; } } else foundactivepeers: csync_group->hasactivepeers = 1; if (active_grouplist && csync_group->myname) { int i=0, gnamelen = strlen(csync_group->gname); while (active_grouplist[i]) { if ( !strncmp(active_grouplist+i, csync_group->gname, gnamelen) && (active_grouplist[i+gnamelen] == ',' || !active_grouplist[i+gnamelen]) ) goto found_asactive; while (active_grouplist[i]) if (active_grouplist[i++]==',') break; } csync_group->myname = 0; found_asactive: ; } } static void new_action() { struct csync_group_action *t = calloc(sizeof(struct csync_group_action), 1); t->next = csync_group->action; t->logfile = "/dev/null"; csync_group->action = t; } static void add_action_pattern(const char *pattern) { struct csync_group_action_pattern *t = calloc(sizeof(struct csync_group_action_pattern), 1); t->star_matches_slashes = !!strstr(pattern, "**"); t->pattern = pattern; t->next = csync_group->action->pattern; csync_group->action->pattern = t; } static void add_action_exec(const char *command) { struct csync_group_action_command *t = calloc(sizeof(struct csync_group_action_command), 1); t->command = command; t->next = csync_group->action->command; csync_group->action->command = t; } static void set_action_logfile(const char *logfile) { csync_group->action->logfile = logfile; } static void set_action_dolocal() { csync_group->action->do_local = 1; } static void set_action_dolocal_only() { csync_group->action->do_local = 1; csync_group->action->do_local_only = 1; } static void set_lock_timeout(const char *timeout) { csync_lock_timeout = atoi(timeout); } static void set_tempdir(const char *tempdir) { csync_tempdir = strdup(tempdir); } static void set_database(const char *filename) { if (!csync_database) csync_database = strdup(filename); } static void new_prefix(const char *pname) { struct csync_prefix *p = calloc(sizeof(struct csync_prefix), 1); p->name = pname; p->next = csync_prefix; csync_prefix = p; } static void new_prefix_entry(char *pattern, char *path) { int i; if (path[0] != '/') csync_fatal("Config error: Prefix '%s' is not an absolute path.\n", path); if (!csync_prefix->path && !fnmatch(pattern, myhostname, 0)) { #if __CYGWIN__ if (isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) { char *new_path, *p; asprintf(&new_path, "/cygdrive/%c/%s", tolower(path[0]), path+3); for (p = new_path; *p; p++) if (*p == '\\') *p = '/'; free(path); path = new_path; } #endif for (i=strlen(path)-1; i>0; i--) if (path[i] == '/') path[i] = 0; else break; csync_debug(2, "Prefix '%s' is set to '%s'.\n", csync_prefix->name, path); csync_prefix->path = path; } else free(path); free(pattern); } static void new_nossl(const char *from, const char *to) { struct csync_nossl *t = calloc(sizeof(struct csync_nossl), 1); t->pattern_from = from; t->pattern_to = to; t->next = csync_nossl; csync_nossl = t; } static void new_ignore(char *propname) { if ( !strcmp(propname, "uid") ) csync_ignore_uid = 1; else if ( !strcmp(propname, "gid") ) csync_ignore_gid = 1; else if ( !strcmp(propname, "mod") ) csync_ignore_mod = 1; else csync_fatal("Config error: Unknown 'ignore' porperty: '%s'.\n", propname); free(propname); } static void disable_cygwin_lowercase_hack() { #ifdef __CYGWIN__ if (csync_lowercyg_used) csync_fatal("Config error: 'nocygwinlowercase' must be at the top of the config file.\n"); csync_lowercyg_disable = 1; #else csync_fatal("Config error: Found 'nocygwinlowercase' but this is not a cygwin csync2.\n"); #endif } %} %expect 2 %union { char *txt; } %token TK_BLOCK_BEGIN TK_BLOCK_END TK_STEND TK_AT TK_AUTO %token TK_NOSSL TK_IGNORE TK_GROUP TK_HOST TK_EXCL TK_INCL TK_COMP TK_KEY TK_DATABASE %token TK_ACTION TK_PATTERN TK_EXEC TK_DOLOCAL TK_LOGFILE TK_NOCYGLOWER %token TK_PREFIX TK_ON TK_COLON TK_POPEN TK_PCLOSE %token TK_BAK_DIR TK_BAK_GEN TK_DOLOCALONLY %token TK_TEMPDIR %token TK_LOCK_TIMEOUT %token TK_STRING %% config: /* empty */ | block config ; block: block_header block_body | TK_PREFIX TK_STRING { new_prefix($2); } TK_BLOCK_BEGIN prefix_list TK_BLOCK_END { } | TK_NOSSL TK_STRING TK_STRING TK_STEND { new_nossl($2, $3); } | TK_DATABASE TK_STRING TK_STEND { set_database($2); } | TK_TEMPDIR TK_STRING TK_STEND { set_tempdir($2); } | TK_IGNORE ignore_list TK_STEND | TK_NOCYGLOWER TK_STEND { disable_cygwin_lowercase_hack(); } | TK_LOCK_TIMEOUT TK_STRING TK_STEND { set_lock_timeout($2); } ; ignore_list: /* empty */ | TK_STRING ignore_list { new_ignore($1); } ; prefix_list: /* empty */ | prefix_list TK_ON TK_STRING TK_COLON TK_STRING TK_STEND { new_prefix_entry($3, on_cygwin_lowercase($5)); } ; block_header: TK_GROUP { new_group(0); } | TK_GROUP TK_STRING { new_group($2); } ; block_body: TK_BLOCK_BEGIN stmts TK_BLOCK_END { check_group(); } ; stmts: /* empty */ | stmt TK_STEND stmts | action stmts ; stmt: TK_HOST host_list | TK_EXCL excl_list | TK_INCL incl_list | TK_COMP comp_list | TK_KEY TK_STRING { set_key($2); } | TK_AUTO TK_STRING { set_auto($2); } | TK_BAK_DIR TK_STRING { set_bak_dir($2); } | TK_BAK_GEN TK_STRING { set_bak_gen($2); } ; host_list: /* empty */ | host_list TK_STRING { add_host($2, strdup($2), 0); } | host_list TK_STRING TK_AT TK_STRING { add_host($2, $4, 0); } | host_list TK_POPEN host_list_slaves TK_PCLOSE host_list ; host_list_slaves: /* empty */ | host_list_slaves TK_STRING { add_host($2, strdup($2), 1); } | host_list_slaves TK_STRING TK_AT TK_STRING { add_host($2, $4, 1); } ; excl_list: /* empty */ | excl_list TK_STRING { add_patt(0, on_cygwin_lowercase($2)); } ; incl_list: /* empty */ | incl_list TK_STRING { add_patt(1, on_cygwin_lowercase($2)); } ; comp_list: /* empty */ | incl_list TK_STRING { add_patt(2, on_cygwin_lowercase($2)); } ; action: TK_ACTION { new_action(); } TK_BLOCK_BEGIN action_stmts TK_BLOCK_END ; action_stmts: /* empty */ | action_stmt TK_STEND action_stmts ; action_stmt: TK_PATTERN action_pattern_list | TK_EXEC action_exec_list | TK_LOGFILE TK_STRING { set_action_logfile($2); } | TK_DOLOCAL { set_action_dolocal(); } | TK_DOLOCALONLY { set_action_dolocal_only(); } ; action_pattern_list: /* empty */ | action_pattern_list TK_STRING { add_action_pattern(on_cygwin_lowercase($2)); } ; action_exec_list: /* empty */ | action_exec_list TK_STRING { add_action_exec($2); } ; csync2-2.0-22-gce67c55/cfgfile_scanner.l000066400000000000000000000060701333746731400175260ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ %{ #include "cfgfile_parser.h" #include #define MAX_INCLUDE_DEPTH 10 YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH]; int include_stack_ptr = 0; #define YY_NO_INPUT 1 #define YY_NO_UNPUT 1 %} %option noyywrap yylineno %option nounput %x STRING INCL %% "{" { return TK_BLOCK_BEGIN; } "}" { return TK_BLOCK_END; } "(" { return TK_POPEN; } ")" { return TK_PCLOSE; } ";" { return TK_STEND; } ":" { return TK_COLON; } "@" { return TK_AT; } "nossl" { return TK_NOSSL; } "ignore" { return TK_IGNORE; } "database" { return TK_DATABASE; } "group" { return TK_GROUP; } "host" { return TK_HOST; } "exclude" { return TK_EXCL; } "include" { return TK_INCL; } "compare" { return TK_COMP; } "key" { return TK_KEY; } "auto" { return TK_AUTO; } "action" { return TK_ACTION; } "pattern" { return TK_PATTERN; } "exec" { return TK_EXEC; } "logfile" { return TK_LOGFILE; } "do-local" { return TK_DOLOCAL; } "do-local-only" { return TK_DOLOCALONLY; } "prefix" { return TK_PREFIX; } "on" { return TK_ON; } "lock-timeout" { return TK_LOCK_TIMEOUT; } "tempdir" { return TK_TEMPDIR; } "backup-directory" { return TK_BAK_DIR; } "backup-generations" { return TK_BAK_GEN; } "no-cygwin-lowercase" { return TK_NOCYGLOWER; } "config" BEGIN(INCL); [ \t]* /* eat the whitespaces */ [^ \t\r\n;]+ { if ( include_stack_ptr >= MAX_INCLUDE_DEPTH ) { fprintf(stderr, "Config includes nested too deeply.\n"); exit(1); } include_stack[include_stack_ptr++] = YY_CURRENT_BUFFER; yyin = fopen(yytext, "r"); if ( !yyin ) { fprintf(stderr, "Can't open included config file '%s'.\n", yytext); exit(1); } yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); BEGIN(0); } ";" BEGIN(0); <> { if ( !include_stack_ptr ) yyterminate(); else { yy_delete_buffer(YY_CURRENT_BUFFER); yy_switch_to_buffer(include_stack[--include_stack_ptr]); BEGIN(INCL); } } \" BEGIN(STRING); [^\"]* { yylval.txt=strdup(yytext); return TK_STRING; } \" BEGIN(0); [ \r\n\t]+ /* whitespaces are just delimiters */ #[^\r\n]* /* this is a comment */ [^ \r\n\t@;\(\)#"]*[^ \r\n\t@:;\(\)#"] { yylval.txt=strdup(yytext); return TK_STRING; } %% csync2-2.0-22-gce67c55/check.c000066400000000000000000000253751333746731400154730ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include #ifdef __CYGWIN__ #include /* This does only check the case of the last filename element. But that should * be OK for us now... */ int csync_cygwin_case_check(const char *filename) { if (!strcmp(filename, "/cygdrive")) goto check_ok; if (!strncmp(filename, "/cygdrive/", 10) && strlen(filename) == 11) goto check_ok; char winfilename[MAX_PATH]; cygwin_conv_to_win32_path(filename, winfilename); int winfilename_len = strlen(winfilename); int found_file_len; HANDLE found_file_handle; WIN32_FIND_DATA fd; /* See if we can find this file. */ found_file_handle = FindFirstFile(winfilename, &fd); if (found_file_handle == INVALID_HANDLE_VALUE) goto check_failed; FindClose(found_file_handle); found_file_len = strlen(fd.cFileName); /* This should never happen. */ if (found_file_len > winfilename_len) goto check_failed; if (strcmp(winfilename + winfilename_len - found_file_len, fd.cFileName)) goto check_failed; check_ok: csync_debug(3, "Cygwin/Win32 filename case check ok: %s (%s)\n", winfilename, filename); return 1; check_failed: csync_debug(2, "Cygwin/Win32 filename case check failed: %s (%s)\n", winfilename, filename); return 0; } #endif /* __CYGWIN__ */ void csync_hint(const char *file, int recursive) { SQL("Adding Hint", "INSERT INTO hint (filename, recursive) " "VALUES ('%s', %d)", url_encode(file), recursive); } void csync_mark(const char *file, const char *thispeer, const char *peerfilter) { struct peer *pl = csync_find_peers(file, thispeer); int pl_idx; csync_schedule_commands(file, thispeer == 0); if ( ! pl ) { csync_debug(2, "Not in one of my groups: %s (%s)\n", file, thispeer ? thispeer : "NULL"); return; } csync_debug(1, "Marking file as dirty: %s\n", file); for (pl_idx=0; pl[pl_idx].peername; pl_idx++) if (!peerfilter || !strcmp(peerfilter, pl[pl_idx].peername)) { SQL("Deleting old dirty file entries", "DELETE FROM dirty WHERE filename = '%s' AND peername = '%s'", url_encode(file), url_encode(pl[pl_idx].peername)); SQL("Marking File Dirty", "INSERT INTO dirty (filename, forced, myname, peername) " "VALUES ('%s', %s, '%s', '%s')", url_encode(file), csync_new_force ? "1" : "0", url_encode(pl[pl_idx].myname), url_encode(pl[pl_idx].peername)); } free(pl); } /* return 0 if path does not contain any symlinks */ int csync_check_pure(const char *filename) { #ifdef __CYGWIN__ // For some reason or another does this function __kills__ // the performance when using large directories with cygwin. // And there are no symlinks in windows anyways.. if (!csync_lowercyg_disable) return 0; #endif struct stat sbuf; int dir_len = 0; int i; int same_len; /* single entry last query cache * to speed up checks from deep subdirs */ static struct { /* store inclusive trailing slash for prefix match */ char *path; /* strlen(path) */ int len; /* cached return value */ int has_symlink; } cached; for (i = 0; filename[i]; i++) if (filename[i] == '/') dir_len = i+1; if (dir_len <= 1) /* '/' a symlink? hardly. */ return 0; /* identical prefix part */ for (i = 0; i < dir_len && i < cached.len; i++) if (filename[i] != cached.path[i]) break; /* backtrack to slash */ for (--i; i >= 0 && cached.path[i] != '/'; --i); ; same_len = i+1; csync_debug(3, " check: %s %u, %s %u, %u.\n", filename, dir_len, cached.path ?: "(null)", cached.len, same_len); /* exact match? */ if (dir_len == same_len && same_len == cached.len) return cached.has_symlink; { /* new block for myfilename[] */ char myfilename[dir_len+1]; char *to_be_cached; int has_symlink = 0; memcpy(myfilename, filename, dir_len); myfilename[dir_len] = '\0'; to_be_cached = strdup(myfilename); i = dir_len-1; while (i) { for (; i && myfilename[i] != '/'; --i) ; if (i <= 1) break; if (i+1 == same_len) { if (same_len == cached.len) { /* exact match */ has_symlink = cached.has_symlink; break; } else if (!cached.has_symlink) /* prefix of something 'pure' */ break; } myfilename[i]=0; if (lstat_strict(prefixsubst(myfilename), &sbuf) || S_ISLNK(sbuf.st_mode)) { has_symlink = 1; break; } } if (to_be_cached) { /* strdup can fail. So what. */ free(cached.path); cached.path = to_be_cached; cached.len = dir_len; cached.has_symlink = has_symlink; } return has_symlink; } } void csync_check_del(const char *file, int recursive, int init_run) { char *where_rec = ""; struct textlist *tl = 0, *t; struct stat st; if ( recursive ) { if ( !strcmp(file, "/") ) ASPRINTF(&where_rec, "OR 1=1"); else ASPRINTF(&where_rec, "UNION ALL SELECT filename from file where filename > '%s/' " "and filename < '%s0'", url_encode(file), url_encode(file)); } SQL_BEGIN("Checking for removed files", "SELECT filename from file where " "filename = '%s' %s ORDER BY filename", url_encode(file), where_rec) { const char *filename = url_decode(SQL_V(0)); if (!csync_match_file(filename)) continue; if ( lstat_strict(prefixsubst(filename), &st) != 0 || csync_check_pure(filename) ) textlist_add(&tl, filename, 0); } SQL_END; for (t = tl; t != 0; t = t->next) { if (!init_run) csync_mark(t->value, 0, 0); SQL("Removing file from DB. It isn't with us anymore.", "DELETE FROM file WHERE filename = '%s'", url_encode(t->value)); } textlist_free(tl); if ( recursive ) free(where_rec); } int csync_check_mod(const char *file, int recursive, int ignnoent, int init_run) { int check_type = csync_match_file(file); int dirdump_this = 0, dirdump_parent = 0; struct dirent **namelist; int n, this_is_dirty = 0; const char *checktxt; struct stat st; if (*file != '%') { struct csync_prefix *p; for (p = csync_prefix; p; p = p->next) { if (!p->path) continue; if (!strcmp(file, p->path)) { char new_file[strlen(p->name) + 3]; sprintf(new_file, "%%%s%%", p->name); csync_debug(2, "Prefix matched: %s <- %s.\n", new_file, file); csync_check_mod(new_file, recursive, ignnoent, init_run); continue; } if (check_type < 1) { int file_len = strlen(file); int path_len = strlen(p->path); if (file_len < path_len && p->path[file_len] == '/' && !strncmp(file, p->path, file_len)) check_type = 1; } } } if ( check_type>0 && lstat_strict(prefixsubst(file), &st) != 0 ) { if ( ignnoent ) return 0; csync_fatal("This should not happen: " "Can't stat %s.\n", prefixsubst(file)); } switch ( check_type ) { case 2: csync_debug(2, "Checking %s.\n", file); checktxt = csync_genchecktxt(&st, file, 0); if (csync_compare_mode) printf("%s\n", file); SQL_BEGIN("Checking File", "SELECT checktxt FROM file WHERE " "filename = '%s'", url_encode(file)) { if ( !csync_cmpchecktxt(checktxt, url_decode(SQL_V(0))) ) { csync_debug(2, "File has changed: %s\n", file); this_is_dirty = 1; } } SQL_FIN { if ( SQL_COUNT == 0 ) { csync_debug(2, "New file: %s\n", file); this_is_dirty = 1; } } SQL_END; if ( this_is_dirty && !csync_compare_mode ) { SQL("Deleting old file entry", "DELETE FROM file WHERE filename = '%s'", url_encode(file)); SQL("Adding or updating file entry", "INSERT INTO file (filename, checktxt) " "VALUES ('%s', '%s')", url_encode(file), url_encode(checktxt)); if (!init_run) csync_mark(file, 0, 0); } dirdump_this = 1; dirdump_parent = 1; /* fall thru */ case 1: if ( !recursive ) break; if ( !S_ISDIR(st.st_mode) ) break; csync_debug(2, "Checking %s%s* ..\n", file, !strcmp(file, "/") ? "" : "/"); n = scandir(prefixsubst(file), &namelist, 0, alphasort); if (n < 0) { csync_debug(0, "%s in scandir: %s (%s)\n", strerror(errno), prefixsubst(file), file); csync_error_count++; } else { while(n--) { on_cygwin_lowercase(namelist[n]->d_name); if ( strcmp(namelist[n]->d_name, ".") && strcmp(namelist[n]->d_name, "..") ) { char fn[strlen(file)+ strlen(namelist[n]->d_name)+2]; sprintf(fn, "%s/%s", !strcmp(file, "/") ? "" : file, namelist[n]->d_name); if (csync_check_mod(fn, recursive, 0, init_run)) dirdump_this = 1; } free(namelist[n]); } free(namelist); } if ( dirdump_this && csync_dump_dir_fd >= 0 ) { int written = 0, len = strlen(file)+1; while (written < len) { int rc = write(csync_dump_dir_fd, file+written, len-written); if (rc <= 0) csync_fatal("Error while writing to dump_dir_fd %d: %s\n", csync_dump_dir_fd, strerror(errno)); written += rc; } } break; default: csync_debug(2, "Don't check at all: %s\n", file); break; } return dirdump_parent; } void csync_check(const char *filename, int recursive, int init_run) { #if __CYGWIN__ if (!strcmp(filename, "/")) { filename = "/cygdrive"; } #endif struct csync_prefix *p = csync_prefix; csync_debug(2, "Running%s check for %s ...\n", recursive ? " recursive" : "", filename); if (!csync_compare_mode) csync_check_del(filename, recursive, init_run); csync_check_mod(filename, recursive, 1, init_run); if (*filename == '/') while (p) { if (p->path) { int p_len = strlen(p->path); int f_len = strlen(filename); if (p_len <= f_len && !strncmp(p->path, filename, p_len) && (filename[p_len] == '/' || !filename[p_len])) { char new_filename[strlen(p->name) + strlen(filename+p_len) + 10]; sprintf(new_filename, "%%%s%%%s", p->name, filename+p_len); csync_debug(2, "Running%s check for %s ...\n", recursive ? " recursive" : "", new_filename); if (!csync_compare_mode) csync_check_del(new_filename, recursive, init_run); csync_check_mod(new_filename, recursive, 1, init_run); } } p = p->next; } } csync2-2.0-22-gce67c55/checktxt.c000066400000000000000000000055701333746731400162260ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include /* * this csync_genchecktxt() function might not be nice or * optimal - but it is hackish and easy to read at the same * time.... ;-) */ #define xxprintf(...) \ { char buffer; /* needed for older glibc */ \ int t = snprintf(&buffer, 0, ##__VA_ARGS__); \ elements[elidx]=alloca(t+1); \ snprintf(elements[elidx], t+1, ##__VA_ARGS__); \ len+=t; elidx++; } const char *csync_genchecktxt(const struct stat *st, const char *filename, int ign_mtime) { static char *buffer = 0; char *elements[64]; int elidx=0, len=1; int i, j, k; /* version 1 of this check text */ xxprintf("v1"); if ( !S_ISLNK(st->st_mode) && !S_ISDIR(st->st_mode) ) xxprintf(":mtime=%Ld", ign_mtime ? (long long)0 : (long long)st->st_mtime); if ( !csync_ignore_mod ) xxprintf(":mode=%d", (int)st->st_mode); if ( !csync_ignore_uid ) xxprintf(":uid=%d", (int)st->st_uid); if ( !csync_ignore_gid ) xxprintf(":gid=%d", (int)st->st_gid); if ( S_ISREG(st->st_mode) ) xxprintf(":type=reg:size=%Ld", (long long)st->st_size); if ( S_ISDIR(st->st_mode) ) xxprintf(":type=dir"); if ( S_ISCHR(st->st_mode) ) xxprintf(":type=chr:dev=%d", (int)st->st_rdev); if ( S_ISBLK(st->st_mode) ) xxprintf(":type=blk:dev=%d", (int)st->st_rdev); if ( S_ISFIFO(st->st_mode) ) xxprintf(":type=fifo"); if ( S_ISLNK(st->st_mode) ) { char tmp[4096]; int r = readlink(filename, tmp, 4095); tmp[ r >= 0 ? r : 0 ] = 0; xxprintf(":type=lnk:target=%s", url_encode(tmp)); } if ( S_ISSOCK(st->st_mode) ) xxprintf(":type=sock"); if ( buffer ) free(buffer); buffer = malloc(len); for (i=j=0; j #include #include #include main() { struct stat st; char tpl[20]="/tmp/test.XXXXXX"; int fd = mkstemp(tpl); if (fd == -1) exit(1); unlink(tpl); if (fstat(fd, &st) != 0) exit(1); if ((st.st_mode & 0777) != 0600) exit(1); exit(0); }], csync_cv_HAVE_SECURE_MKSTEMP=yes, csync_cv_HAVE_SECURE_MKSTEMP=no, csync_cv_HAVE_SECURE_MKSTEMP=cross)]) if test x"$csync_cv_HAVE_SECURE_MKSTEMP" = x"yes"; then case $host_os in hpux*) dnl HP-UX has a broken mkstemp() implementation they refuse to fix, dnl so we noisily skip using it. See HP change request JAGaf34426 dnl for details. (sbonds) AC_MSG_WARN(Skipping broken HP-UX mkstemp() -- using mktemp() instead) ;; *) AC_DEFINE(HAVE_SECURE_MKSTEMP, 1, [Define to 1 if mkstemp() is available and works right]) ;; esac fi # check for large file support AC_SYS_LARGEFILE # Check for librsync. AC_ARG_WITH([librsync-source], AS_HELP_STRING([--with-librsync-source=source-tar-file], [build this librsync and link statically against it (hack! hack!)]), AC_SUBST([librsync_source_file], $withval), AC_CHECK_LIB([rsync], [rs_sig_file], , [AC_MSG_ERROR(librsync is required)]) ) AM_CONDITIONAL([PRIVATE_LIBRSYNC], [test -n "$librsync_source_file"]) AC_ARG_ENABLE([sqlite], [AC_HELP_STRING([--enable-sqlite], [enable/disable sqlite 2 support (default is disabled)])], [], [ enable_sqlite=no ]) if test "$enable_sqlite" == yes then AC_CHECK_HEADERS([sqlite.h], , [AC_MSG_ERROR([[SQLite header not found; install libsqlite-dev and dependencies for SQLite 2 support]])]) AC_DEFINE([HAVE_SQLITE], 1, [Define if sqlite 2 support is wanted]) fi AC_ARG_ENABLE([sqlite3], [AC_HELP_STRING([--disable-sqlite3], [enable/disable sqlite3 support (default is enabled)])], [], [ enable_sqlite3=yes ]) if test "$enable_sqlite3" == yes then AC_CHECK_HEADERS([sqlite3.h], , [AC_MSG_ERROR([[SQLite header not found; install libsqlite3-dev and dependencies for SQLite 3 support]])]) AC_DEFINE([HAVE_SQLITE3], 1, [Define if sqlite3 support is wanted]) fi AC_ARG_ENABLE([gnutls], [AS_HELP_STRING([--disable-gnutls],[enable/disable GNU TLS support (default is enabled)])], [], [ enable_gnutls=yes ]) if test "$enable_gnutls" != no then PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= 2.6.0], [ AC_DEFINE([HAVE_LIBGNUTLS], 1, [Define to 1 when using GNU TLS library]) ]) fi AC_ARG_ENABLE([mysql], [AC_HELP_STRING([--enable-mysql], [enable/disable MySQL support (default is disabled)])], [], [ enable_mysql=no ]) AC_ARG_ENABLE([postgres], [AC_HELP_STRING([--enable-postgres], [enable/disable Postgres support (default is disabled)])], [], [ enable_postgres=no ]) if test "$enable_mysql" == yes then # Check for mysql. # This is a bloody hack for fedora core CFLAGS="$CFLAGS `mysql_config --cflags`" # Check MySQL development header AC_CHECK_HEADERS([mysql/mysql.h], , [AC_MSG_ERROR([[mysql header not found; install mysql-devel and dependencies for MySQL Support]])]) AC_DEFINE([HAVE_MYSQL], 1, [Define if mysql support is wanted]) fi if test "$enable_postgres" == yes then AC_CHECK_HEADERS([libpq-fe.h], , [AC_MSG_ERROR([[postgres header not found; install libpq-dev and dependencies for Postgres support]])]) AC_DEFINE([HAVE_POSTGRES], 1, [Define if postgres support is wanted]) fi # at least one db backend must be configured. if test "$enable_postgres" != yes && test "$enable_mysql" != yes && test "$enable_sqlite3" != yes && test "$enable_sqlite" != yes then AC_MSG_ERROR([No database backend configured. Please enable either sqlite, sqlite3, mysql or postgres.]) fi AC_CONFIG_FILES([Makefile]) AC_OUTPUT csync2-2.0-22-gce67c55/conn.c000066400000000000000000000363061333746731400153470ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBGNUTLS # include # include #endif int conn_fd_in = -1; int conn_fd_out = -1; int conn_clisok = 0; #ifdef HAVE_LIBGNUTLS int csync_conn_usessl = 0; static gnutls_session_t conn_tls_session; static gnutls_certificate_credentials_t conn_x509_cred; #endif static const char *__response[] = { [CR_OK_CMD_FINISHED] = "OK (cmd_finished).", [CR_OK_DATA_FOLLOWS] = "OK (data_follows).", [CR_OK_SEND_DATA] = "OK (send_data).", [CR_OK_NOT_FOUND] = "OK (not_found).", [CR_OK_PATH_NOT_FOUND] = "OK (path_not_found).", [CR_OK_CU_LATER] = "OK (cu_later).", [CR_OK_ACTIVATING_SSL] = "OK (activating_ssl).", /* CR_ERROR: all sorts of strings; often strerror(errno) */ /* more specific errors: */ [CR_ERR_CONN_CLOSED] = "Connection closed.", [CR_ERR_ALSO_DIRTY_HERE] = "File is also marked dirty here!", [CR_ERR_PARENT_DIR_MISSING] = "Parent dir missing.", [CR_ERR_GROUP_LIST_ALREADY_SET] = "Group list already set!", [CR_ERR_IDENTIFICATION_FAILED] = "Identification failed!", [CR_ERR_PERM_DENIED_FOR_SLAVE] = "Permission denied for slave!", [CR_ERR_PERM_DENIED] = "Permission denied!", [CR_ERR_SSL_EXPECTED] = "SSL encrypted connection expected!", [CR_ERR_UNKNOWN_COMMAND] = "Unkown command!", [CR_ERR_WIN32_EIO_CREATE_DIR] = "Win32 I/O Error on CreateDirectory()", }; static const int __response_size = sizeof(__response)/sizeof(__response[0]); const char *conn_response(unsigned i) { if (i < __response_size && __response[i] && __response[i][0]) return __response[i]; csync_fatal("BUG! No such response: %u\n", i); return NULL; } static const unsigned int response_len(unsigned i) { static unsigned int __response_len[sizeof(__response)/sizeof(__response[0])]; static int initialized; if (!initialized) { unsigned int j; for (j = 0; j < __response_size; j++) __response_len[j] = strlen(__response[j] ?: ""); initialized = 1; } return (i < __response_size) ? __response_len[i] : 0; } enum connection_response conn_response_to_enum(const char *response) { unsigned int i, len; for (i = 0; i < __response_size; i++) { len = response_len(i); if (len && !strncmp(__response[i], response, len)) return i; } /* may be a new OK code? */ if (!strncmp(response, "OK (", 4)) return CR_OK; else return CR_ERROR; } static void csync_client_bind(int sfd, struct addrinfo *peer_ai) { struct addrinfo hints; struct addrinfo *result, *rp; int s; if (!bind_to_myhostname) return; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = peer_ai->ai_family; hints.ai_socktype = SOCK_STREAM; s = getaddrinfo(bind_to_myhostname ? myhostname : NULL, 0, &hints, &result); if (s != 0) { csync_debug(1, "Cannot prepare local socket for bind, getaddrinfo: %s\n", gai_strerror(s)); return; } for (rp = result; rp != NULL; rp = rp->ai_next) { if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Success */ } if (rp != NULL) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; if (getnameinfo(rp->ai_addr, rp->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) csync_debug(1, "Bound to %s:%s as %s.\n", hbuf, sbuf, myhostname); else /* WTF, is failure even possible here? * Anyways, bind() did not report an error. */ csync_debug(1, "Bound local socket as %s.\n", myhostname); } else /* So bind() failed. Ignore, and try to connect anyways. * Maybe it still works, maybe identification paranoia of the * peer will kick us out. */ csync_debug(1, "Local socket bind() to %s failed; ignored.\n", myhostname); freeaddrinfo(result); /* No longer needed */ } /* getaddrinfo stuff mostly copied from its manpage */ int conn_connect(const char *peername) { struct addrinfo hints; struct addrinfo *result, *rp; int save_errno = 0; int sfd = -1, s; /* Obtain address(es) matching host/port */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ s = getaddrinfo(peername, csync_port, &hints, &result); if (s != 0) { csync_debug(1, "Cannot resolve peername, getaddrinfo: %s\n", gai_strerror(s)); return -1; } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; /* If called with -N somehostname, try to bind to that name. * If that fails, ignore, and try the connect anyways. */ csync_client_bind(sfd, rp); if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; /* Success */ save_errno = errno; close(sfd); sfd = -1; } if (sfd != -1) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; if (getnameinfo(rp->ai_addr, rp->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) csync_debug(1, "Connect to %s:%s (%s).\n", hbuf, sbuf, peername); else /* WTF, is failure even possible here? */ csync_debug(1, "Connect to :%s (%s).\n", csync_port, peername); } freeaddrinfo(result); /* No longer needed */ if (sfd == -1 && save_errno) errno = save_errno; return sfd; } int conn_open(const char *peername) { int on = 1; conn_fd_in = conn_connect(peername); if (conn_fd_in < 0) { csync_debug(1, "Can't create socket: %s\n", strerror(errno)); return -1; } if (setsockopt(conn_fd_in, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on) ) < 0) { csync_debug(2, "Can't set TCP_NODELAY option on TCP socket.\n"); close(conn_fd_in); conn_fd_in = -1; return -1; } conn_fd_out = conn_fd_in; conn_clisok = 1; #ifdef HAVE_LIBGNUTLS csync_conn_usessl = 0; #endif return 0; } int conn_set(int infd, int outfd) { int on = 1; conn_fd_in = infd; conn_fd_out = outfd; conn_clisok = 1; #ifdef HAVE_LIBGNUTLS csync_conn_usessl = 0; #endif // when running in server mode, this has been done already // in csync2.c with more restrictive error handling.. // FIXME don't even try in "ssh" mode if ( setsockopt(conn_fd_out, IPPROTO_TCP, TCP_NODELAY, &on, (socklen_t) sizeof(on)) < 0 ) csync_debug(2, "Can't set TCP_NODELAY option on TCP socket.\n"); return 0; } #ifdef HAVE_LIBGNUTLS static void ssl_log(int level, const char* msg) { csync_debug(level, "%s", msg); } int conn_activate_ssl(int server_role) { gnutls_alert_description_t alrt; char *ssl_keyfile; char *ssl_certfile; int err; if (csync_conn_usessl) return 0; ASPRINTF(&ssl_keyfile, "%s/csync2_ssl_key.pem", systemdir); ASPRINTF(&ssl_certfile, "%s/csync2_ssl_cert.pem", systemdir); gnutls_global_init(); gnutls_global_set_log_function(ssl_log); gnutls_global_set_log_level(10); gnutls_certificate_allocate_credentials(&conn_x509_cred); err = gnutls_certificate_set_x509_key_file(conn_x509_cred, ssl_certfile, ssl_keyfile, GNUTLS_X509_FMT_PEM); if(err != GNUTLS_E_SUCCESS) { gnutls_certificate_free_credentials(conn_x509_cred); gnutls_global_deinit(); csync_fatal( "SSL: failed to use key file %s and/or certificate file %s: %s (%s)\n", ssl_keyfile, ssl_certfile, gnutls_strerror(err), gnutls_strerror_name(err) ); } if(server_role) { gnutls_certificate_free_cas(conn_x509_cred); if(gnutls_certificate_set_x509_trust_file(conn_x509_cred, ssl_certfile, GNUTLS_X509_FMT_PEM) < 1) { gnutls_certificate_free_credentials(conn_x509_cred); gnutls_global_deinit(); csync_fatal( "SSL: failed to use certificate file %s as CA.\n", ssl_certfile ); } } else gnutls_certificate_free_ca_names(conn_x509_cred); gnutls_init(&conn_tls_session, (server_role ? GNUTLS_SERVER : GNUTLS_CLIENT)); gnutls_priority_set_direct(conn_tls_session, "PERFORMANCE", NULL); gnutls_credentials_set(conn_tls_session, GNUTLS_CRD_CERTIFICATE, conn_x509_cred); if(server_role) { gnutls_certificate_send_x509_rdn_sequence(conn_tls_session, 0); gnutls_certificate_server_set_request(conn_tls_session, GNUTLS_CERT_REQUIRE); } gnutls_transport_set_ptr2( conn_tls_session, (gnutls_transport_ptr_t)(long)conn_fd_in, (gnutls_transport_ptr_t)(long)conn_fd_out ); err = gnutls_handshake(conn_tls_session); switch(err) { case GNUTLS_E_SUCCESS: break; case GNUTLS_E_WARNING_ALERT_RECEIVED: alrt = gnutls_alert_get(conn_tls_session); fprintf( csync_debug_out, "SSL: warning alert received from peer: %d (%s).\n", alrt, gnutls_alert_get_name(alrt) ); break; case GNUTLS_E_FATAL_ALERT_RECEIVED: alrt = gnutls_alert_get(conn_tls_session); fprintf( csync_debug_out, "SSL: fatal alert received from peer: %d (%s).\n", alrt, gnutls_alert_get_name(alrt) ); default: gnutls_bye(conn_tls_session, GNUTLS_SHUT_RDWR); gnutls_deinit(conn_tls_session); gnutls_certificate_free_credentials(conn_x509_cred); gnutls_global_deinit(); csync_fatal( "SSL: handshake failed: %s (%s)\n", gnutls_strerror(err), gnutls_strerror_name(err) ); } csync_conn_usessl = 1; return 0; } int conn_check_peer_cert(const char *peername, int callfatal) { const gnutls_datum_t *peercerts; unsigned npeercerts; int i, cert_is_ok = -1; if (!csync_conn_usessl) return 1; peercerts = gnutls_certificate_get_peers(conn_tls_session, &npeercerts); if(peercerts == NULL || npeercerts == 0) { if (callfatal) csync_fatal("Peer did not provide an SSL X509 cetrificate.\n"); csync_debug(1, "Peer did not provide an SSL X509 cetrificate.\n"); return 0; } { char certdata[2*peercerts[0].size + 1]; for (i=0; i total) { n = write(conn_fd_out, ((char *) buf) + total, count - total); if (n >= 0) total += n; else { if (errno == EINTR) continue; else return -1; } } return total; } } int conn_raw_read(void *buf, size_t count) { static char buffer[512]; static int buf_start=0, buf_end=0; if ( buf_start == buf_end ) { if (count > 128) return READ(buf, count); else { buf_start = 0; buf_end = READ(buffer, 512); if (buf_end < 0) { buf_end=0; return -1; } } } if ( buf_start < buf_end ) { size_t real_count = buf_end - buf_start; if ( real_count > count ) real_count = count; memcpy(buf, buffer+buf_start, real_count); buf_start += real_count; return real_count; } return 0; } struct conn_debug_buf { char *pos; size_t rlen; }; static void buf_printf(struct conn_debug_buf *b, const char *fmt, ...) { va_list ap; int c; if (b->rlen <= 0) return; va_start(ap, fmt); c = vsnprintf(b->pos, b->rlen, fmt, ap); va_end(ap); if (c >= b->rlen) { b->pos[b->rlen - 1] = '\0'; c = b->rlen; } if (c >= 0) { b->rlen -= c; b->pos += c; } } void conn_debug(const char *name, const char*buf, size_t count) { char buffer[1024]; struct conn_debug_buf b = { .pos = buffer, .rlen = sizeof(buffer) }; int i; if ( csync_debug_level < 3 ) return; if (csync_server_child_pid) buf_printf(&b, "<%d> ", csync_server_child_pid); buf_printf(&b, "%s> ", name); for (i=0; i= 127) buf_printf(&b, "\\%03o", buf[i]); else buf_printf(&b, "%c", buf[i]); break; } } fprintf(csync_debug_out, "%s\n", buffer); } int conn_read(void *buf, size_t count) { int pos, rc; for (pos=0; pos < count; pos+=rc) { rc = conn_raw_read(buf+pos, count-pos); if (rc <= 0) return pos; } conn_debug("Peer", buf, pos); return pos; } int conn_write(const void *buf, size_t count) { conn_debug("Local", buf, count); return WRITE(buf, count); } void conn_printf(const char *fmt, ...) { char dummy, *buffer; va_list ap; int size; va_start(ap, fmt); size = vsnprintf(&dummy, 1, fmt, ap); buffer = alloca(size+1); va_end(ap); va_start(ap, fmt); vsnprintf(buffer, size+1, fmt, ap); va_end(ap); conn_write(buffer, size); } size_t conn_gets(char *s, size_t size) { size_t i=0; while (i 1 && s[i-1] != '\n') csync_fatal("Received line too long for buffer size (%u), giving up.\n", size); return i; } csync2-2.0-22-gce67c55/contrib/000077500000000000000000000000001333746731400156765ustar00rootroot00000000000000csync2-2.0-22-gce67c55/contrib/csync2id.pl000066400000000000000000000250521333746731400177550ustar00rootroot00000000000000#!/usr/bin/perl -w # Copyright: telegraaf (NL) # Author: ard@telegraafnet.nl # License: GPL v2 or higher use strict; use Linux::Inotify2; use Data::Dumper; use File::Find; use POSIX qw(uname :sys_wait_h); use Sys::Syslog; use Net::Server::Daemonize qw(daemonize); use IO::Select; use Fcntl; my $program="csync2id"; my $daemonize=1; my $usesyslog=1; my $pidfile='/var/run/csync2id.pid'; my $pidfileboot='/var/run/csync2id.boot.pid'; ################################################################################ # Config items # Overridden by /etc/csync2id.cfg # Normal config in /etc/csync2id.cfg: # # @::dirs=qw( /data1 /data2 ); # 1; # # csyncdirhint: preferred hint command for directories (a single directory name # will be added) # csyncfilehint: preferred hint command for files (at most $x filenames will be appended) # csynccheck: preferred command scheduled right after the hint, or after a timeout # csyncupdate: preferred command scheduled right after the check # debug: log debug lines # statsdir: file to log the number of watched directories # statchanges: file to log the number of file change events # statsretry: file to log the number of retries needed so far for the hint # dirs: an array of directories which need to be watched recursively ################################################################################ $::csynchintmaxargs=20; @::csyncdirhint=("/usr/sbin/csync2", "-B","-A","-rh"); @::csyncfilehint=("/usr/sbin/csync2", "-B","-A","-h"); @::csynccheck=("/usr/sbin/csync2", "-B","-A","-c"); @::csyncupdate=("/usr/sbin/csync2", "-B","-A","-u"); $::debug=3; $::statsdir="/dev/shm/csyncstats/dirs"; $::statschanges="/dev/shm/csyncstats/changes"; $::statsretry="/dev/shm/csyncstats/retry"; @::dirs=(); require "/etc/csync2id.cfg"; $daemonize && daemonize(0,0,$pidfileboot); $usesyslog && openlog("$program",'pid','daemon'); use constant { LOGERR => 0, LOGWARN => 1, LOGINFO =>2, LOGDEBUG=>3,LOGSLOTS=>256 }; my %prios=( 0 => 'err', 1 => 'warning', 2 => 'info', default => 'debug' ); sub logger { my($level,@args)=@_; my ($prio)=$prios{$level}||$prios{'default'}; # :$prios{'default'}; if($usesyslog) { syslog($prio,@args) if (($level<= LOGDEBUG && $level<=$::debug)||($::debug>=LOGDEBUG && $level&$::debug)) } else { print "LOG: $prio "; print(@args); print "\n"; } } logger(LOGDEBUG,Dumper(\@::dirs)); my $inotify = new Linux::Inotify2 or ( logger(LOGERR, "Unable to create new inotify object: $!") && die("inotify") ); # For stats my $globaldirs=0; my $globalevents=0; my $globalhintretry=0; sub logstatsline { my ($file,$line)=@_; # open STATS,"> $file"; # print STATS $line; # close STATS; } #package Runner; ################################################################################ # Process runner # Runs processes and keep status # API: # runstatus: current status of a runslot (running/idle) # exitstatus: last status of an exec # slotrun: forkexec a new command with a callback when it's finished for a specific slot # Helpers: # reaper is the SIGCHLD handler # checkchildren should be called after syscalls which exited with E_INTR, and # calls the specific callbacks. ################################################################################ use constant { RUN_IDLE => 0, RUN_RUNNING => 1, RUN_REAPED =>2 }; my %slotstatus; my %slotexitstatus; my %slotcommandline; my %slotcallback; my %slotpid2slot; my %slotstarttime; # pid queue for reaper # Every pid (key) contains a waitforpid exit status as value. my %slotpidreaped; sub runstatus { my ($slot)=@_; return($slotstatus{$slot}) if exists($slotstatus{$slot}); return RUN_IDLE; } sub slotrun { my ($slot,$callback,$commandline)=(@_); $SIG{CHLD} = \&reaper; if(runstatus($slot)!=RUN_IDLE) { logger(LOGDEBUG,"SlotRun: Asked to run for $slot, but $slot != RUN_IDLE"); return -1; } $slotcommandline{$slot}=$commandline; $slotcallback{$slot}=$callback; $slotstatus{$slot}=RUN_RUNNING; $slotstarttime{$slot}=time(); my $pid=fork(); if(!$pid) { # We know that exec should not return. Now tell the perl interpreter that we know. { exec(@$commandline); } logger(LOGWARN,"SlotRun: $slot Exec failed: ".join(' ','>', @$commandline,'<')); # If we can't exec, we don't really know why, and we don't want to go busy fork execing # Give a fork exec grace by waiting sleep 1; exit 1; } logger(LOGDEBUG,"SlotRun: $slot # ".$pid.": run".join(' ','>', @$commandline,'<')); $slotpid2slot{$pid}=$slot; } sub exitstatus { my ($slot)=@_; return($slotexitstatus{$slot}) if exists($slotexitstatus{$slot}); return -1; } sub reaper { } sub checkchildren { if($::debug==LOGSLOTS) { while(my ($slot,$status) = each %slotstatus) { logger(LOGDEBUG,"SlotRun: $slot status $status time: ".($status?(time()-$slotstarttime{$slot}):'x')); }; } while() { my ($pid)=waitpid(-1,&WNOHANG); if($pid<=0) { last; } my $status=$?; if (WIFEXITED($status)||WIFSIGNALED($status) && exists($slotpid2slot{$pid})) { my $slot=$slotpid2slot{$pid}; delete($slotpid2slot{$pid}); $slotstatus{$slot}=RUN_IDLE; $slotexitstatus{$slot}=$status; logger(LOGDEBUG, "SlotRun: $slot $pid exited with $status == ".WEXITSTATUS($status).".\n"); # Callback determines if we run again or not. $slotcallback{$slot}->($slot,$slotexitstatus{$slot},$slotcommandline{$slot}); } else { logger(LOGDEBUG, "SlotRun: Unknown process $pid change state.\n"); } } } ################################################################################ # CSYNC RUNNERS # groups queued hints into single csync commands # run csync update and check commands ################################################################################ # use constant { CSYNCHINT => 0 , CSYNCCHECK=>1 , CSYNCUPDATE=>2 }; my @hintfifo; sub updateCallback { my ($slot,$exitstatus,$command)=@_; if($exitstatus) { logger(LOGWARN,"Updater got ".$exitstatus.", NOT retrying run:".join(' ','>',@$command,'<')); } } sub runupdater { if(runstatus('csupdate') == RUN_IDLE) { slotrun('csupdate',\&updateCallback,\@::csyncupdate); } } sub checkerCallback { my ($slot,$exitstatus,$command)=@_; if($exitstatus) { logger(LOGWARN,"Checker got ".$exitstatus.", NOT retrying run:".join(' ','>',@$command,'<')); } runupdater(); } sub runchecker { if(runstatus('cscheck') == RUN_IDLE) { slotrun('cscheck',\&checkerCallback,\@::csynccheck); } } sub hinterCallback { my ($slot,$exitstatus,$command)=@_; if($exitstatus) { logger(LOGWARN,"Hinter got ".$exitstatus.", retrying run:".join(' ','>',@$command,'<')); $globalhintretry++; logstatsline($::statsretry,$globalhintretry); slotrun($slot,\&hinterCallback,$command); } else { runchecker(); } } sub givehints { if(runstatus('cshint') == RUN_IDLE && @hintfifo) { # PREPARE JOB # Directories should be treated with care, one at a time. my @hintcommand; if($hintfifo[0]->{'recurse'}) { my $filename=$hintfifo[0]->{'filename'}; @hintcommand=(@::csyncdirhint,$filename); shift(@hintfifo) while (@hintfifo && $filename eq $hintfifo[0]->{'filename'} ); } else { # Files can be bulked, until the next directory my $nrargs=0; @hintcommand=(@::csyncfilehint); while($nrargs < $::csynchintmaxargs && @hintfifo && !$hintfifo[0]->{'recurse'}) { my $filename=$hintfifo[0]->{'filename'}; push(@hintcommand,$filename); shift(@hintfifo) while (@hintfifo && $filename eq $hintfifo[0]->{'filename'} ); $nrargs++; } } slotrun('cshint',\&hinterCallback,\@hintcommand); } } ################################################################################ # Subtree parser # Adds subtrees to an existing watch # globals: $globaldirs for stats. # Logs to logger ################################################################################ sub watchtree { my ($inotifier,$tree,$inotifyflags) = @_; $inotifier->watch ($tree, $inotifyflags); $globaldirs++; find( sub { if(! m/^\.\.?$/) { my ($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_) ; if(-d _ ) { if ($nlink==2) { $File::Find::prune = 1; } $inotifier->watch ($File::Find::dir.'/'.$_, $inotifyflags) or die("WatchTree: watch creation failed (maybe increase the number of watches?)"); $globaldirs++; logger(LOGDEBUG,"WatchTree: directory ". $globaldirs." ".$File::Find::dir.'/'.$_); } } }, $tree ); logstatsline($::statsdir,$globaldirs); } ################################################################################ # Main # logger(LOGINFO, 'Main: Starting $Id: csync2id.pl,v 1.18 2008/12/24 15:34:19 ard Exp $'); # Start watching the directories logger(LOGINFO, "Main: traversing directories"); eval { watchtree($inotify,$_,IN_MOVE|IN_DELETE|IN_CLOSE_WRITE|IN_ATTRIB|IN_CREATE) foreach(@::dirs) }; if($@) { logger(LOGERR,"Main: $@"); exit(2); } logger(LOGINFO,"Main: ready for events"); # Kill other daemon because we are ready if($daemonize) { if ( -e $pidfile ) { my $thepid; @ARGV=($pidfile); $thepid=<>; logger(LOGINFO, "Main: about to kill previous incarnation $thepid"); kill(15,$thepid); sleep 0.5; } rename($pidfileboot,$pidfile); } # Main loop $inotify->blocking(O_NONBLOCK); my $timeout=20; while () { #my ($rhset,$dummy,$dummy,$timeleft)=IO::Select->select($selectset, undef, undef, 60); my $nfound; my $rin=''; vec($rin,$inotify->fileno,1)=1; ($nfound,$timeout)=select($rin, undef, undef, $timeout); logger(LOGDEBUG,"Main: nrfds: $nfound timeleft: $timeout\n"); if(!$timeout) { $timeout=20; logger(LOGDEBUG, "Main: timeout->check and update"); runchecker(); runupdater(); # } if($nfound>0) { my @events = $inotify->read; unless (@events > 0) { logger(LOGWARN,"Main: Zero events, must be a something weird"); } foreach(@events) { if($_->IN_Q_OVERFLOW) { logger(LOGERR,"Main: FATAL:inotify queue overflow: csync2id was to slow to handle events"); } if( $_->IN_ISDIR) { my $recurse=0; # We want to recurse only for new, renamed or deleted directories $recurse=$_->IN_DELETE||$_->IN_CREATE||$_->IN_MOVED_TO||$_->IN_MOVED_FROM; eval watchtree($inotify,$_->fullname,IN_MOVE|IN_DELETE|IN_CLOSE_WRITE|IN_ATTRIB|IN_CREATE) if $_->IN_CREATE||$_->IN_MOVED_TO; if($@) { logger(LOGINFO,"$@"); exit(3); } push(@hintfifo,{ "filename" => $_->fullname , "recurse" => $recurse }); logger(LOGDEBUG,"Main: dir: ".$_->mask." ".$recurse." ".$_->fullname); } else { # Accumulate single file events: next if(@hintfifo && $hintfifo[-1]->{"filename"} eq $_->fullname); push(@hintfifo,{ "filename" => $_->fullname , "recurse" => 0 }); logger(LOGDEBUG,"Main: file: ".$_->mask," ".$_->fullname); } $globalevents++; } } checkchildren(); givehints(); logstatsline($::statschanges,$globalevents); } csync2-2.0-22-gce67c55/copycheck.sh000077500000000000000000000013541333746731400165500ustar00rootroot00000000000000#!/bin/bash errors=0 current_year=$(date +%Y) check() { years=`seq 2003 $current_year` for y in $(git log --date=short --pretty="format:%ad" -- $1 | cut -d- -f1 | sort -u) do years=`echo $years | sed "s,$y,,"` copyright=`perl -ne '/([*#]|^).*Copyright.*/ or next; s{\b(\d\d\d\d)\s*-\s*(\d\d\d\d)\b} {join ", ", $1 .. $2}ge; print;' $1` case $copyright in *Copyright*$y*) :;; *) echo "Missing $y in $1." (( errors++ )) ;; esac done for y in $years do case $copyright in *Copyright*$y*) echo "Bogus $y in $1." (( errors++ )) ;; esac done } for f in ${1:- $(git ls-files | xargs grep -l Copyright)} ; do check $f done if [ $errors -ne 0 ]; then echo "Found $errors errors." exit 1 fi exit 0 csync2-2.0-22-gce67c55/csync2-compare000077500000000000000000000041251333746731400170130ustar00rootroot00000000000000#!/bin/bash verbose=0 if [ "$1" = "-v" ]; then verbose=1 shift fi if [ $# != 3 ]; then echo "Usage: $0 [-v] host1[@host1] host2[@host2] basedir" >&2 exit 1 fi left1="${1%@*}" left2="${1#*@}" right1="${2%@*}" right2="${2#*@}" basedir="$3" left_cmd="ssh $left1 'csync2 -or $basedir -P $right2 | sort | xargs md5sum'" right_cmd="ssh $right1 'csync2 -or $basedir -P $left2 | sort | xargs md5sum'" if [ $verbose -eq 1 ]; then echo echo "L: $left_cmd" echo "R: $right_cmd" echo fi my_md5sum='perl -w -e '\'' use strict; use Digest::MD5; foreach my $f (@ARGV) { if (-l $f) { print "LINK:", Digest::MD5->new->add(readlink($f))->hexdigest, " $f\n"; next; } if (-f $f) { open(FILE, $f) or die "Can not open >>$f<<: $!"; binmode(FILE); print "DATA:", Digest::MD5->new->addfile(*FILE)->hexdigest, " $f\n"; close(FILE); next; } print "SPECIALFILE:0 $f\n"; } '\' tic="'" my_md5sum="${my_md5sum//$tic/$tic\\$tic$tic}" left_cmd="${left_cmd/md5sum/$my_md5sum}" right_cmd="${right_cmd/md5sum/$my_md5sum}" diff -u <( eval "$left_cmd" ) <( eval "$right_cmd" ) | awk ' function isort(A, n, i, j, hold) { for (i=1; i hold) { j--; A[j+1] = A[j]; } A[j] = hold; } } /^-[a-zA-Z0-9]/ { gotsomething=1; if ('$verbose') print; sub(/^./, ""); all[$2] = 1; left[$2] = $1; } /^\+[a-zA-Z0-9]/ { gotsomething=1; if ('$verbose') print; sub(/^./, ""); all[$2] = 1; right[$2] = $1; } END { outcount = 0; for (filename in all) { outlines[filename] = sprintf("%s %s %s", (left[filename] == "" ? "-" : "X"), (right[filename] == "" ? "-" : "X"), filename); sortindex[outcount] = filename; outcount++; } if ('$verbose' && gotsomething) printf "\n"; isort(sortindex, outcount); for (i=0; i:@]/[database] pgsql://[:@]/[database] .IP If .B database is not given, it defaults to .B csync2_ .IP Note that for non-sqlite backends, the database name is "cleaned", characters outside of [0-9][a-z][A-Z] will be replaced with _. .SS "Creating key file:" .IP .B csync2 -k filename .SS "Environment variables:" .IP CSYNC2_SYSTEM_DIR .IP Directory containing csync2.cfg and other csync2 system files. Defaults to /etc. .SS "Note:" Csync2 will refuse to do anything if this file is found: $CSYNC2_SYSTEM_DIR/csync2.lock .SH "SEE ALSO" .PD 0 .TP http://oss.linbit.com/csync2/paper.pdf .TP http://git.linbit.com/csync2.git/?a=blob;f=AUTHORS .PD .PP This manual page is a hand-edited help2man processed csync2 help. csync2-2.0-22-gce67c55/csync2.c000066400000000000000000000623051333746731400156110ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "db_api.h" #include #ifdef REAL_DBDIR # undef DBDIR # define DBDIR REAL_DBDIR #endif char *csync_database = 0; int db_type = DB_SQLITE3; static char *file_config = 0; char *cfgname = ""; char *systemdir = NULL; /* ETCDIR */ char *lockfile = NULL; /* ETCDIR/csync2.lock */ char myhostname[256] = ""; int bind_to_myhostname = 0; char *csync_port = "30865"; char *active_grouplist = 0; char *active_peerlist = 0; extern int yyparse(); extern FILE *yyin; int csync_error_count = 0; int csync_debug_level = 0; FILE *csync_debug_out = 0; int csync_syslog = 0; int csync_server_child_pid = 0; int csync_timestamps = 0; int csync_new_force = 0; int csync_dump_dir_fd = -1; enum { MODE_NONE, MODE_HINT, MODE_CHECK, MODE_CHECK_AND_UPDATE, MODE_UPDATE, MODE_INETD, MODE_SERVER, MODE_SINGLE, MODE_MARK, MODE_FORCE, MODE_LIST_HINT, MODE_LIST_FILE, MODE_LIST_SYNC, MODE_TEST_SYNC, MODE_LIST_DIRTY, MODE_REMOVE_OLD, MODE_COMPARE, MODE_SIMPLE }; void help(char *cmd) { printf( "\n" PACKAGE_STRING " - cluster synchronization tool, 2nd generation\n" "Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH\n" " http://www.linbit.com\n" "See also: http://git.linbit.com/csync2.git/?a=blob;f=AUTHORS\n" "\n" "Version: " CSYNC2_VERSION "\n" "\n" "This program is free software under the terms of the GNU GPL.\n" "\n" "Usage: %s [-v..] [-C config-name] \\\n" " [-D database-dir] [-N hostname] [-p port] ..\n" "\n" "With file parameters:\n" " -h [-r] file.. Add (recursive) hints for check to db\n" " -c [-r] file.. Check files and maybe add to dirty db\n" " -u [-d] [-r] file.. Updates files if listed in dirty db\n" " -o [-r] file.. Create list of files in compare-mode\n" " -f [-r] file.. Force files to win next conflict resolution\n" " -m file.. Mark files in database as dirty\n" "\n" "Simple mode:\n" " -x [-d] [[-r] file..] Run checks for all given files and update\n" " remote hosts.\n" "\n" "Without file parameters:\n" " -c Check all hints in db and eventually mark files as dirty\n" " -u [-d] Update (transfer dirty files to peers and mark as clear)\n" "\n" " -H List all pending hints from status db\n" " -L List all file-entries from status db\n" " -M List all dirty files from status db\n" "\n" " -S myname peername List file-entries from status db for this\n" " synchronization pair.\n" "\n" " -T Test if everything is in sync with all peers.\n" "\n" " -T filename Test if this file is in sync with all peers.\n" "\n" " -T myname peername Test if this synchronization pair is in sync.\n" "\n" " -T myname peer file Test only this file in this sync pair.\n" "\n" " -TT As -T, but print the unified diffs.\n" "\n" " -R Remove files from database which do not match config entries.\n" "\n" " -i Run in inetd server mode.\n" " -ii Run in stand-alone server mode.\n" " -iii Run in stand-alone server mode (one connect only).\n" "\n" " -l Send some messages to syslog instead of stderr to not clobber\n" " the protocol in case stdout and stderr point to the same fd.\n" " Default for inetd mode.\n" "\n" "Exit codes:\n" " The modes -H, -L, -M and -S return 2 if the requested db is empty.\n" " The mode -T returns 2 if both hosts are in sync.\n" " Otherwise, only exit codes 0 (no errors)\n" " and 1 (some unspecified errrors) are expected.\n" "\n" "Modifiers:\n" " -r Recursive operation over subdirectories\n" " -d Dry-run on all remote update operations\n" "\n" " -B Do not block everything into big SQL transactions. This\n" " slows down csync2 but allows multiple csync2 processes to\n" " access the database at the same time. Use e.g. when slow\n" " lines are used or huge files are transferred.\n" "\n" " -A Open database in asynchronous mode. This will cause data\n" " corruption if the operating system crashes or the computer\n" " loses power.\n" "\n" " -N address When running in stand-alone mode with -ii bind to a\n" " specific interface. You can pass either a hostname or ip\n" " address. If used, this value must match exactly the host\n" " value in each csync2.cfg file.\n" "\n" " -I Init-run. Use with care and read the documentation first!\n" " You usually do not need this option unless you are\n" " initializing groups with really large file lists.\n" "\n" " -X Also add removals to dirty db when doing a -TI run.\n" " -U Don't mark all other peers as dirty when doing a -TI run.\n" "\n" " -G Group1,Group2,Group3,...\n" " Only use these groups from config-file.\n" "\n" " -P peer1,peer1,...\n" " Only update these peers (still mark all as dirty).\n" " Only show files for these peers in -o (compare) mode.\n" "\n" " -F Add new entries to dirty database with force flag set.\n" "\n" " -t Print timestamps to debug output (e.g. for profiling).\n" "\n" " -s filename\n" " Print timestamps also to this file.\n" "\n" " -W fd Write a list of directories in which relevant files can be\n" " found to the specified file descriptor (when doing a -c run).\n" " The directory names in this output are zero-terminated.\n" "\n" "Database switches:\n" " -D database-dir or url\n" " default: /var/lib/csync2\n" " Absolute path: use sqlite database in that directory\n" " URLs:\n" " sqlite:///some/path[/database.db3]\n" " sqlite3:///some/path[/database.db3]\n" " sqlite2:///some/path[/database.db]\n" " mysql://[:@]/[database]\n" " pgsql://[:@]/[database]\n" " If database is not given, it defaults to csync2_.\n" " Note that for non-sqlite backends, the database name is \"cleaned\",\n" " characters outside of [0-9][a-z][A-Z] will be replaced with _.\n" "\n" "Creating key file:\n" " %s -k filename\n" "\n" "Environment variables:\n" " CSYNC2_SYSTEM_DIR\n" " Directory containing csync2.cfg and other csync2 system files.\n" " Defaults to " ETCDIR ".\n" "\n" "Csync2 will refuse to do anything if this file is found:\n" "$CSYNC2_SYSTEM_DIR/csync2.lock\n" "\n", cmd, cmd); exit(1); } int create_keyfile(const char *filename) { int fd = open(filename, O_WRONLY|O_CREAT|O_EXCL, 0600); int rand = open("/dev/urandom", O_RDONLY); char matrix[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; unsigned char key[64 /* plus newline */ +1]; unsigned char key_bin[48 /* (sizeof(key)*8)/6 */]; int i, j; int rc; assert(sizeof(matrix) == 65); if ( fd == -1 ) { fprintf(stderr, "Can't create key file: %s\n", strerror(errno)); return 1; } if ( rand == -1 ) { fprintf(stderr, "Can't open /dev/urandom: %s\n", strerror(errno)); return 1; } rc = read(rand, key_bin, sizeof(key_bin)); if (rc != sizeof(key_bin)) { fprintf(stderr, "Failed to read %zu bytes from /dev/urandom: %s\n", sizeof(key_bin), rc == -1 ? strerror(errno) : "short read?"); return -1; } close(rand); for (i = j = 0; i < sizeof(key)/4*4; i+=4, j+=3) { key[i+0] = matrix[ key_bin[j] & 63]; key[i+1] = matrix[((key_bin[j] >> 6)|(key_bin[j+1] <<2)) & 63]; key[i+2] = matrix[((key_bin[j+1] >> 4)|(key_bin[j+2] <<4)) & 63]; key[i+3] = matrix[ (key_bin[j+2] >> 2) & 63]; } key[sizeof(key) -1] = '\n'; errno = 0; rc = write(fd, key, sizeof(key)); if (close(fd) || rc != sizeof(key)) { fprintf(stderr, "Failed to write out keyfile: %s\n", errno ? strerror(errno) : "short write?"); unlink(filename); } return 0; } static int csync_server_bind(void) { struct linger sl = { 1, 5 }; struct addrinfo hints; struct addrinfo *result, *rp; int save_errno; int sfd = -1, s, off = 0, on = 1; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; s = getaddrinfo(bind_to_myhostname ? myhostname : NULL, csync_port, &hints, &result); if (s != 0) { csync_debug(1, "Cannot prepare local socket, getaddrinfo: %s\n", gai_strerror(s)); return -1; } /* getaddrinfo() returns a list of address structures. Try each address until we successfully bind(2). If socket(2) (or bind(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, (socklen_t) sizeof(on)) < 0) goto error; if (setsockopt(sfd, SOL_SOCKET, SO_LINGER, &sl, (socklen_t) sizeof(sl)) < 0) goto error; if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &on, (socklen_t) sizeof(on)) < 0) goto error; if (rp->ai_family == AF_INET6) if (setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, &off, (socklen_t) sizeof(off)) < 0) goto error; if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Success */ close(sfd); sfd = -1; } if (sfd != -1) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; if (getnameinfo(rp->ai_addr, rp->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) csync_debug(1, "Listening on %s:%s as %s.\n", hbuf, sbuf, myhostname); else /* WTF, is failure even possible here? */ csync_debug(1, "Listening on :%s as %s.\n", csync_port, myhostname); } freeaddrinfo(result); /* No longer needed */ return sfd; error: save_errno = errno; close(sfd); errno = save_errno; return -1; } static int csync_server_loop(int single_connect) { union { struct sockaddr sa; struct sockaddr_in sa_in; struct sockaddr_in6 sa_in6; struct sockaddr_storage ss; } addr; int listenfd = csync_server_bind(); if (listenfd < 0) goto error; if (listen(listenfd, 5) < 0) goto error; /* we want to "cleanly" shutdown if the connection is lost unexpectedly */ signal(SIGPIPE, SIG_IGN); /* server is not interested in its childs, prevent zombies */ signal(SIGCHLD, SIG_IGN); printf("Csync2 daemon running. Waiting for connections.\n"); while (1) { unsigned addrlen = sizeof(addr); int conn = accept(listenfd, &addr.sa, &addrlen); if (conn < 0) goto error; fflush(stdout); fflush(stderr); if (single_connect) close(listenfd); if (single_connect || !fork()) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; /* need to restore default SIGCHLD handler in the session, * as we may need to wait on them in action.c */ signal(SIGCHLD, SIG_DFL); csync_server_child_pid = getpid(); if (getnameinfo(&addr.sa, addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) != 0) goto error; csync_debug(1, "New connection from %s:%s.\n", hbuf, sbuf); dup2(conn, 0); dup2(conn, 1); close(conn); return 0; } close(conn); } error: csync_debug(0, "Server error: %s\n", strerror(errno)); return 1; } void csync_openlog(void) { csync_syslog = 1; openlog("csync2", LOG_ODELAY | LOG_PID, LOG_LOCAL0); } int main(int argc, char ** argv) { struct textlist *tl = 0, *t; int mode = MODE_NONE; int mode_test_auto_diff = 0; int init_run = 0; int init_run_with_removals = 0; int init_run_straight = 0; int recursive = 0; int retval = -1; int dry_run = 0; int opt, i; csync_debug_out = stderr; if ( argc==3 && !strcmp(argv[1], "-k") ) { return create_keyfile(argv[2]); } systemdir = getenv("CSYNC2_SYSTEM_DIR"); if (!systemdir) systemdir = ETCDIR; ASPRINTF(&lockfile, "%s/csync2.lock", systemdir); if (!access(lockfile , F_OK)) { printf("Found %s\n", lockfile); return 1; } while ( (opt = getopt(argc, argv, "W:s:Ftp:G:P:C:D:N:HBAIXULlSTMRvhcuoimfxrd")) != -1 ) { switch (opt) { case 'W': csync_dump_dir_fd = atoi(optarg); if (write(csync_dump_dir_fd, 0, 0) < 0) csync_fatal("Invalid dump_dir_fd %d: %s\n", csync_dump_dir_fd, strerror(errno)); break; case 's': csync_timestamp_out = fopen(optarg, "a"); if (!csync_timestamp_out) csync_fatal("Can't open timestanp file `%s': %s\n", optarg, strerror(errno)); break; case 'F': csync_new_force = 1; break; case 't': csync_timestamps = 1; break; case 'p': csync_port = strdup(optarg); break; case 'G': active_grouplist = optarg; break; case 'P': active_peerlist = optarg; break; case 'B': db_blocking_mode = 0; break; case 'A': db_sync_mode = 0; break; case 'I': init_run = 1; break; case 'X': init_run_with_removals = 1; break; case 'U': init_run_straight = 1; break; case 'C': cfgname = optarg; break; case 'D': csync_database = optarg; break; case 'N': snprintf(myhostname, 256, "%s", optarg); ++bind_to_myhostname; break; case 'v': csync_debug_level++; break; case 'l': csync_openlog(); break; case 'h': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_HINT; break; case 'x': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_SIMPLE; break; case 'c': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_CHECK; break; case 'u': if ( mode == MODE_CHECK ) mode = MODE_CHECK_AND_UPDATE; else { if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_UPDATE; } break; case 'o': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_COMPARE; break; case 'i': if ( mode == MODE_INETD ) mode = MODE_SERVER; else if ( mode == MODE_SERVER ) mode = MODE_SINGLE; else { if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_INETD; } break; case 'm': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_MARK; break; case 'f': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_FORCE; break; case 'H': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_LIST_HINT; break; case 'L': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_LIST_FILE; break; case 'S': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_LIST_SYNC; break; case 'T': if ( mode == MODE_TEST_SYNC ) { mode_test_auto_diff = 1; } else { if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_TEST_SYNC; } break; case 'M': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_LIST_DIRTY; break; case 'R': if ( mode != MODE_NONE ) help(argv[0]); mode = MODE_REMOVE_OLD; break; case 'r': recursive = 1; break; case 'd': dry_run = 1; break; default: help(argv[0]); } } if ( optind < argc && mode != MODE_HINT && mode != MODE_MARK && mode != MODE_FORCE && mode != MODE_SIMPLE && mode != MODE_UPDATE && mode != MODE_CHECK && mode != MODE_COMPARE && mode != MODE_CHECK_AND_UPDATE && mode != MODE_LIST_SYNC && mode != MODE_TEST_SYNC) help(argv[0]); if ( mode == MODE_TEST_SYNC && optind != argc && optind+1 != argc && optind+2 != argc && optind+3 != argc) help(argv[0]); if ( mode == MODE_LIST_SYNC && optind+2 != argc ) help(argv[0]); if ( mode == MODE_NONE ) help(argv[0]); /* Some inetd connect stderr to stdout. The debug level messages on * stderr would confuse the csync2 protocol. Log to syslog instead. */ if ( mode == MODE_INETD && !csync_syslog ) csync_openlog(); if ( *myhostname == 0 ) { gethostname(myhostname, 256); myhostname[255] = 0; } for (i=0; myhostname[i]; i++) myhostname[i] = tolower(myhostname[i]); /* Stand-alone server mode. This is a hack.. */ if ( mode == MODE_SERVER || mode == MODE_SINGLE ) { if (csync_server_loop(mode == MODE_SINGLE)) return 1; mode = MODE_INETD; } // print time (if -t is set) csync_printtime(); /* In inetd mode we need to read the module name from the peer * before we open the config file and database */ if ( mode == MODE_INETD ) { static char line[4 * 4096]; char *cmd, *para; /* configure conn.c for inetd mode */ conn_set(0, 1); if ( !conn_gets(line, sizeof(line)) ) return 0; cmd = strtok(line, "\t \r\n"); para = cmd ? strtok(0, "\t \r\n") : 0; if (cmd && !strcasecmp(cmd, "ssl")) { #ifdef HAVE_LIBGNUTLS conn_resp(CR_OK_ACTIVATING_SSL); conn_activate_ssl(1); if ( !conn_gets(line, sizeof(line)) ) return 0; cmd = strtok(line, "\t \r\n"); para = cmd ? strtok(0, "\t \r\n") : 0; #else conn_printf("This csync2 server is built without SSL support.\n"); return 0; #endif } if (!cmd || strcasecmp(cmd, "config")) { conn_printf("Expecting SSL (optional) and CONFIG as first commands.\n"); return 0; } if (para) cfgname = strdup(url_decode(para)); } if ( !*cfgname ) { ASPRINTF(&file_config, "%s/csync2.cfg", systemdir); } else { int i; for (i=0; cfgname[i]; i++) if ( !(cfgname[i] >= '0' && cfgname[i] <= '9') && !(cfgname[i] >= 'a' && cfgname[i] <= 'z') ) { (mode == MODE_INETD ? conn_printf : csync_fatal) ("Config names are limited to [a-z0-9]+.\n"); return mode != MODE_INETD; } ASPRINTF(&file_config, "%s/csync2_%s.cfg", systemdir, cfgname); } csync_debug(2, "Config-File: %s\n", file_config); yyin = fopen(file_config, "r"); if ( !yyin ) csync_fatal("Can not open config file `%s': %s\n", file_config, strerror(errno)); yyparse(); fclose(yyin); if (!csync_database || !csync_database[0] || csync_database[0] == '/') csync_database = db_default_database(csync_database); csync_debug(2, "My hostname is %s.\n", myhostname); csync_debug(2, "Database-File: %s\n", csync_database); { const struct csync_group *g; for (g=csync_group; g; g=g->next) if ( g->myname ) goto found_a_group; csync_fatal("This host (%s) is not a member of any configured group.\n", myhostname); found_a_group:; } csync_db_open(csync_database); for (i=optind; i < argc; i++) on_cygwin_lowercase(argv[i]); switch (mode) { case MODE_SIMPLE: if ( argc == optind ) { csync_check("/", 1, init_run); csync_update(0, 0, 0, dry_run); } else { char *realnames[argc-optind]; for (i=optind; i < argc; i++) { realnames[i-optind] = strdup(getrealfn(argv[i])); csync_check_usefullness(realnames[i-optind], recursive); csync_check(realnames[i-optind], recursive, init_run); } csync_update((const char**)realnames, argc-optind, recursive, dry_run); for (i=optind; i < argc; i++) free(realnames[i-optind]); } break; case MODE_HINT: for (i=optind; i < argc; i++) { char *realname = getrealfn(argv[i]); csync_check_usefullness(realname, recursive); csync_hint(realname, recursive); } break; case MODE_CHECK: case MODE_CHECK_AND_UPDATE: if ( argc == optind ) { SQL_BEGIN("Check all hints", "SELECT filename, recursive FROM hint") { textlist_add(&tl, url_decode(SQL_V(0)), atoi(SQL_V(1))); } SQL_END; for (t = tl; t != 0; t = t->next) { csync_check(t->value, t->intvalue, init_run); SQL("Remove processed hint.", "DELETE FROM hint WHERE filename = '%s' " "and recursive = %d", url_encode(t->value), t->intvalue); } textlist_free(tl); } else { for (i=optind; i < argc; i++) { char *realname = getrealfn(argv[i]); csync_check_usefullness(realname, recursive); csync_check(realname, recursive, init_run); } } if (mode != MODE_CHECK_AND_UPDATE) break; case MODE_UPDATE: if ( argc == optind ) { csync_update(0, 0, 0, dry_run); } else { char *realnames[argc-optind]; for (i=optind; i < argc; i++) { realnames[i-optind] = strdup(getrealfn(argv[i])); csync_check_usefullness(realnames[i-optind], recursive); } csync_update((const char**)realnames, argc-optind, recursive, dry_run); for (i=optind; i < argc; i++) free(realnames[i-optind]); } break; case MODE_COMPARE: csync_compare_mode = 1; for (i=optind; i < argc; i++) { char *realname = getrealfn(argv[i]); csync_check_usefullness(realname, recursive); csync_check(realname, recursive, init_run); } break; case MODE_INETD: conn_resp(CR_OK_CMD_FINISHED); csync_daemon_session(); break; case MODE_MARK: for (i=optind; i < argc; i++) { char *realname = getrealfn(argv[i]); char *pfname; csync_check_usefullness(realname, recursive); pfname=strdup(prefixencode(realname)); csync_mark(pfname, 0, 0); if ( recursive ) { char *where_rec = ""; if ( !strcmp(realname, "/") ) ASPRINTF(&where_rec, "or 1=1"); else ASPRINTF(&where_rec, "UNION ALL SELECT filename from file where filename > '%s/' " "and filename < '%s0'", url_encode(pfname), url_encode(pfname)); SQL_BEGIN("Adding dirty entries recursively", "SELECT filename FROM file WHERE filename = '%s' %s", url_encode(pfname), where_rec) { char *filename = strdup(url_decode(SQL_V(0))); csync_mark(filename, 0, 0); free(filename); } SQL_END; } free(pfname); } break; case MODE_FORCE: for (i=optind; i < argc; i++) { char *realname = getrealfn(argv[i]); char *pfname = strdup(prefixencode(realname)); char *where_rec = ""; if ( recursive ) { if ( !strcmp(realname, "/") ) ASPRINTF(&where_rec, "or 1=1"); else ASPRINTF(&where_rec, "or (filename > '%s/' " "and filename < '%s0')", url_encode(pfname), url_encode(pfname)); } SQL("Mark file as to be forced", "UPDATE dirty SET forced = 1 WHERE filename = '%s' %s", url_encode(pfname), where_rec); if ( recursive ) free(where_rec); free(pfname); } break; case MODE_LIST_HINT: retval = 2; SQL_BEGIN("DB Dump - Hint", "SELECT recursive, filename FROM hint ORDER BY filename") { printf("%s\t%s\n", (char*)SQL_V(0), url_decode(SQL_V(1))); retval = -1; } SQL_END; break; case MODE_LIST_FILE: retval = 2; SQL_BEGIN("DB Dump - File", "SELECT checktxt, filename FROM file ORDER BY filename") { if (csync_find_next(0, url_decode(SQL_V(1)))) { printf("%s\t%s\n", url_decode(SQL_V(0)), url_decode(SQL_V(1))); retval = -1; } } SQL_END; break; case MODE_LIST_SYNC: retval = 2; SQL_BEGIN("DB Dump - File", "SELECT checktxt, filename FROM file ORDER BY filename") { if ( csync_match_file_host(url_decode(SQL_V(1)), argv[optind], argv[optind+1], 0) ) { printf("%s\t%s\n", url_decode(SQL_V(0)), url_decode(SQL_V(1))); retval = -1; } } SQL_END; break; case MODE_TEST_SYNC: { char *realname; char *pfname; if (init_run && init_run_with_removals) init_run |= 2; if (init_run && init_run_straight) init_run |= 4; switch (argc-optind) { case 3: realname = getrealfn(argv[optind+2]); csync_check_usefullness(realname, 0); pfname=strdup(prefixencode(realname)); if ( mode_test_auto_diff ) { csync_compare_mode = 1; retval = csync_diff(argv[optind], argv[optind+1], pfname); } else if ( csync_insynctest(argv[optind], argv[optind+1], init_run, 0, pfname) ) retval = 2; free(pfname); break; case 2: if ( csync_insynctest(argv[optind], argv[optind+1], init_run, mode_test_auto_diff, 0) ) retval = 2; break; case 1: realname = getrealfn(argv[optind]); csync_check_usefullness(realname, 0); pfname=strdup(prefixencode(realname)); if ( mode_test_auto_diff ) csync_compare_mode = 1; if ( csync_insynctest_all(init_run, mode_test_auto_diff, pfname) ) retval = 2; free(pfname); break; case 0: if ( csync_insynctest_all(init_run, mode_test_auto_diff, 0) ) retval = 2; break; } break; } case MODE_LIST_DIRTY: retval = 2; SQL_BEGIN("DB Dump - Dirty", "SELECT forced, myname, peername, filename FROM dirty ORDER BY filename") { if (csync_find_next(0, url_decode(SQL_V(3)))) { printf("%s\t%s\t%s\t%s\n", atoi(SQL_V(0)) ? "force" : "chary", url_decode(SQL_V(1)), url_decode(SQL_V(2)), url_decode(SQL_V(3))); retval = -1; } } SQL_END; break; case MODE_REMOVE_OLD: if ( active_grouplist ) csync_fatal("Never run -R with -G!\n"); csync_remove_old(); break; } csync_run_commands(); csync_db_close(); csync_debug(1, "Connection closed.\n"); if ( csync_error_count != 0 || (csync_messages_printed && csync_debug_level) ) csync_debug(0, "Finished with %d errors.\n", csync_error_count); csync_printtotaltime(); if ( retval >= 0 && csync_error_count == 0 ) return retval; return csync_error_count != 0; } csync2-2.0-22-gce67c55/csync2.cfg000066400000000000000000000024571333746731400161300ustar00rootroot00000000000000# Csync2 Example Configuration File # --------------------------------- # # Please read the documentation: # http://oss.linbit.com/csync2/paper.pdf # group mygroup # { # host host1 host2 (host3); # host host4@host4-eth2; # # key /etc/csync2.key_mygroup; # # # # # WARNING: # # You CANNOT use paths containing a symlink # # component in include/exclude options! # # # # Here is a real-life example: # # Suppose you have some 64bit Linux systems # # and /usr/lib/ocf is what you want to keep # # in sync. On 64bit Linux systems, /usr/lib # # is usually a symlink to /usr/lib64. # # This does not work: # # include /usr/lib/ocf; # # But this does work: # # include /usr/lib64/ocf; # # # # include /etc/apache; # include %homedir%/bob; # exclude %homedir%/bob/temp; # exclude *~ .*; # # action # { # pattern /etc/apache/httpd.conf; # pattern /etc/apache/sites-available/*; # exec "/usr/sbin/apache2ctl graceful"; # logfile "/var/log/csync2_action.log"; # do-local; # # you can use do-local-only if the execution # # should be done locally only # # do-local-only; # } # # # The backup-directory needs to be created first! # backup-directory /var/backups/csync2; # backup-generations 3; # # auto none; # } # # prefix homedir # { # on host[12]: /export/users; # on *: /home; # } csync2-2.0-22-gce67c55/csync2.h000066400000000000000000000304471333746731400156200ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CSYNC2_H #define CSYNC2_H 1 #define CSYNC2_VERSION "2.0" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "config.h" #include #include #include #include #include #define DB_SCHEMA_VERSION 0 /* asprintf with test for no memory */ #define ASPRINTF(s, fmt, ...) do {\ int __ret = asprintf(s, fmt, ##__VA_ARGS__);\ if (__ret < 0) \ csync_fatal("Out of memory in asprintf at %s:%d\n", __FILE__, __LINE__);\ } while (0) #define VASPRINTF(s, fmt, args) do {\ int __ret = vasprintf(s, fmt, args);\ if (__ret < 0) \ csync_fatal("Out of memory in vasprintf at %s:%d\n", __FILE__, __LINE__);\ } while (0) /* action.c */ extern void csync_schedule_commands(const char *filename, int islocal); extern int csync_check_pure(const char *filename); extern void csync_run_commands(); /* groups.c */ struct peer { const char *myname; const char *peername; }; extern const struct csync_group *csync_find_next(const struct csync_group *g, const char *file); extern int csync_match_file(const char *file); extern void csync_check_usefullness(const char *file, int recursive); extern int csync_match_file_host(const char *file, const char *myname, const char *peername, const char **keys); extern struct peer *csync_find_peers(const char *file, const char *thispeer); extern const char *csync_key(const char *hostname, const char *filename); extern int csync_perm(const char *filename, const char *key, const char *hostname); /* error.c */ extern void csync_printtime(); extern void csync_printtotaltime(); extern void csync_fatal(const char *fmt, ...); extern void csync_debug(int lv, const char *fmt, ...); #define csync_debug_ping(N) \ csync_debug(N, "--> %s %d\n", __FILE__, __LINE__) /* conn.c */ extern int conn_open(const char *peername); extern int conn_set(int infd, int outfd); extern int conn_activate_ssl(int server_role); extern int conn_check_peer_cert(const char *peername, int callfatal); extern int conn_close(); extern int conn_read(void *buf, size_t count); extern int conn_write(const void *buf, size_t count); extern void conn_printf(const char *fmt, ...); extern int conn_fgets(char *s, int size); extern size_t conn_gets(char *s, size_t size); /* Protocol encodes responses in plain text, * everything starting with "OK (" is, well, OK. * * Anything not starting with that is an error response. * In a few cases, behaviour differes depending on the * exact error response string. * * Enum values are irrelevant for ABI (not communicated, only used internally), * you may introduce more specific codes and string values, as long as you * keep OK/ERR enums below/above CR_ERROR. * * See also: conn_response() and friends. */ enum connection_response { CR_OK = 0, /* OK response codes. They need all to start with "OK (" */ CR_OK_CMD_FINISHED, /* "OK (cmd_finished).", */ CR_OK_DATA_FOLLOWS, /* "OK (data_follows).", */ CR_OK_SEND_DATA, /* "OK (send_data).", */ CR_OK_NOT_FOUND, /* "OK (not_found).", */ CR_OK_PATH_NOT_FOUND, /* "OK (path_not_found).", */ CR_OK_CU_LATER, /* "OK (cu_later).", */ CR_OK_ACTIVATING_SSL, /* "OK (activating_ssl).", */ CR_ERROR, /* special error codes, MUST NOT start with "OK (" */ CR_ERR_CONN_CLOSED, CR_ERR_ALSO_DIRTY_HERE, /* "File is also marked dirty here!", */ CR_ERR_PARENT_DIR_MISSING, /* "Parent dir missing.", */ CR_ERR_GROUP_LIST_ALREADY_SET, CR_ERR_IDENTIFICATION_FAILED, CR_ERR_PERM_DENIED_FOR_SLAVE, CR_ERR_PERM_DENIED, CR_ERR_SSL_EXPECTED, CR_ERR_UNKNOWN_COMMAND, CR_ERR_WIN32_EIO_CREATE_DIR, }; static inline int is_ok_response(enum connection_response r) { return r >= CR_OK && r < CR_ERROR; } /* converts to on-the-wire textual representation */ extern const char *conn_response(enum connection_response); /* converts from on-the-wire textual representation */ extern enum connection_response conn_response_to_enum(const char *); static inline void conn_resp(const int r) { conn_printf("%s\n", conn_response(r)); } static inline void conn_resp_zero(const int r) { conn_printf("%s\n---\noctet-stream 0\n", conn_response(r)); } /* db.c */ extern void csync_db_open(const char *file); extern void csync_db_close(); extern void csync_db_sql(const char *err, const char *fmt, ...); extern void* csync_db_begin(const char *err, const char *fmt, ...); extern int csync_db_next(void *vmx, const char *err, int *pN, const char ***pazValue, const char ***pazColName); extern void csync_db_fin(void *vmx, const char *err); extern const void * csync_db_colblob(void *stmtx,int col); extern char *db_default_database(char *dbdir); #define SQL(e, s, ...) csync_db_sql(e, s, ##__VA_ARGS__) #if 0 #if defined(HAVE_LIBSQLITE) #define SQL_BEGIN(e, s, ...) \ { \ char *SQL_ERR = e; \ void *SQL_VM = csync_db_begin(SQL_ERR, s, ##__VA_ARGS__); \ int SQL_COUNT = 0; \ while (1) { \ const char **dataSQL_V, **dataSQL_N; \ int SQL_C; \ if ( !csync_db_next(SQL_VM, SQL_ERR, \ &SQL_C, &dataSQL_V, &dataSQL_N) ) break; \ SQL_COUNT++; #define SQL_V(col) \ (dataSQL_V[(col)]) #endif #endif // #if defined(HAVE_LIBSQLITE3) #define SQL_BEGIN(e, s, ...) \ { \ char *SQL_ERR = e; \ void *SQL_VM = csync_db_begin(SQL_ERR, s, ##__VA_ARGS__); \ int SQL_COUNT = 0; \ \ if (SQL_VM) { \ while (1) { \ const char **dataSQL_V, **dataSQL_N; \ int SQL_C; \ if ( !csync_db_next(SQL_VM, SQL_ERR, \ &SQL_C, &dataSQL_V, &dataSQL_N) ) break; \ SQL_COUNT++; #define SQL_V(col) \ (csync_db_colblob(SQL_VM,(col))) // #endif #define SQL_FIN }{ #define SQL_END \ } \ csync_db_fin(SQL_VM, SQL_ERR); \ } \ } extern int db_blocking_mode; extern int db_sync_mode; /* rsync.c */ extern int csync_rs_check(const char *filename, int isreg); extern void csync_rs_sig(const char *filename); extern int csync_rs_delta(const char *filename); extern int csync_rs_patch(const char *filename); extern int mkpath(const char *path, mode_t mode); extern void split_dirname_basename(char *dirname, char* basename, const char *filepath); /* checktxt.c */ extern const char *csync_genchecktxt(const struct stat *st, const char *filename, int ign_mtime); extern int csync_cmpchecktxt(const char *a, const char *b); /* check.c */ extern void csync_hint(const char *file, int recursive); extern void csync_check(const char *filename, int recursive, int init_run); extern void csync_mark(const char *file, const char *thispeer, const char *peerfilter); /* update.c */ extern void csync_update(const char **patlist, int patnum, int recursive, int dry_run); extern int csync_diff(const char *myname, const char *peername, const char *filename); extern int csync_insynctest(const char *myname, const char *peername, int init_run, int auto_diff, const char *filename); extern int csync_insynctest_all(int init_run, int auto_diff, const char *filename); extern void csync_remove_old(); /* daemon.c */ extern void csync_daemon_session(); extern int csync_copy_file(int fd_in, int fd_out); /* getrealfn.c */ extern char *getrealfn(const char *filename); /* urlencode.c */ /* only use this functions if you understood the sideeffects of the ringbuffer * used to allocate the return values. */ const char *url_encode(const char *in); const char *url_decode(const char *in); /* prefixsubst.c */ /* another ringbuffer here. so use it with care!! */ const char *prefixsubst(const char *in); const char *prefixencode(const char *filename); /* textlist implementation */ struct textlist; struct textlist { struct textlist *next; int intvalue; char *value; char *value2; }; static inline void textlist_add(struct textlist **listhandle, const char *item, int intitem) { struct textlist *tmp = *listhandle; *listhandle = malloc(sizeof(struct textlist)); (*listhandle)->intvalue = intitem; (*listhandle)->value = strdup(item); (*listhandle)->value2 = 0; (*listhandle)->next = tmp; } static inline void textlist_add2(struct textlist **listhandle, const char *item, const char *item2, int intitem) { struct textlist *tmp = *listhandle; *listhandle = malloc(sizeof(struct textlist)); (*listhandle)->intvalue = intitem; (*listhandle)->value = strdup(item); (*listhandle)->value2 = strdup(item2); (*listhandle)->next = tmp; } static inline void textlist_free(struct textlist *listhandle) { struct textlist *next; while (listhandle != 0) { next = listhandle->next; free(listhandle->value); if ( listhandle->value2 ) free(listhandle->value2); free(listhandle); listhandle = next; } } /* config structures */ struct csync_nossl; struct csync_group; struct csync_group_host; struct csync_group_pattern; struct csync_group_host { struct csync_group_host *next; const char *hostname; int on_left_side; int slave; }; struct csync_group_pattern { struct csync_group_pattern *next; int isinclude, iscompare, star_matches_slashes; const char *pattern; }; struct csync_group_action_pattern { struct csync_group_action_pattern *next; int star_matches_slashes; const char *pattern; }; struct csync_group_action_command { struct csync_group_action_command *next; const char *command; }; struct csync_group_action { struct csync_group_action *next; struct csync_group_action_pattern *pattern; struct csync_group_action_command *command; const char *logfile; int do_local; int do_local_only; }; struct csync_group { struct csync_group *next; struct csync_group_host *host; struct csync_group_pattern *pattern; struct csync_group_action *action; const char *key, *myname, *gname; int auto_method, local_slave; const char *backup_directory; int backup_generations; int hasactivepeers; }; struct csync_prefix { const char *name, *path; struct csync_prefix *next; }; struct csync_nossl { struct csync_nossl *next; const char *pattern_from; const char *pattern_to; }; enum CSYNC_AUTO_METHOD { CSYNC_AUTO_METHOD_NONE, CSYNC_AUTO_METHOD_FIRST, CSYNC_AUTO_METHOD_YOUNGER, CSYNC_AUTO_METHOD_OLDER, CSYNC_AUTO_METHOD_BIGGER, CSYNC_AUTO_METHOD_SMALLER, CSYNC_AUTO_METHOD_LEFT, CSYNC_AUTO_METHOD_RIGHT, CSYNC_AUTO_METHOD_LEFT_RIGHT_LOST }; /* global variables */ extern struct csync_group *csync_group; extern struct csync_prefix *csync_prefix; extern struct csync_nossl *csync_nossl; extern unsigned csync_lock_timeout; extern char *csync_tempdir; extern char *csync_database; extern int csync_error_count; extern int csync_debug_level; extern int csync_syslog; extern FILE *csync_debug_out; extern long csync_last_printtime; extern FILE *csync_timestamp_out; extern int csync_messages_printed; extern int csync_server_child_pid; extern int csync_timestamps; extern int csync_new_force; extern char myhostname[]; extern int bind_to_myhostname; extern char *csync_port; extern char *active_grouplist; extern char *active_peerlist; extern char *systemdir; extern char *cfgname; extern int csync_ignore_uid; extern int csync_ignore_gid; extern int csync_ignore_mod; extern int csync_dump_dir_fd; extern int csync_compare_mode; #ifdef HAVE_LIBGNUTLS extern int csync_conn_usessl; #endif #ifdef __CYGWIN__ extern int csync_lowercyg_disable; extern int csync_lowercyg_used; extern int csync_cygwin_case_check(const char *filename); #endif static inline int lstat_strict(const char *filename, struct stat *buf) { #ifdef __CYGWIN__ if (csync_lowercyg_disable && !csync_cygwin_case_check(filename)) { errno = ENOENT; return -1; } #endif return lstat(filename, buf); } static inline char *on_cygwin_lowercase(char *s) { #ifdef __CYGWIN__ if (!csync_lowercyg_disable) { int i; for (i=0; s[i]; i++) s[i] = tolower(s[i]); } csync_lowercyg_used = 1; #endif return s; } #endif /* CSYNC2_H */ csync2-2.0-22-gce67c55/csync2.spec000066400000000000000000000056711333746731400163240ustar00rootroot00000000000000# csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # spec file for package csync2 (Version 2.0) # # norootforbuild # neededforbuild openssl openssl-devel BuildRequires: sqlite-devel sqlite librsync gnutls-devel librsync-devel Name: csync2 License: GPL Group: System/Monitoring Requires: sqlite openssl librsync Autoreqprov: on Version: 2.0 Release: 1 Source0: csync2-%{version}.tar.gz URL: http://oss.linbit.com/csync2 BuildRoot: %{_tmppath}/%{name}-%{version}-build Summary: Cluster sync tool %description Csync2 is a cluster synchronization tool. It can be used to keep files on multiple hosts in a cluster in sync. Csync2 can handle complex setups with much more than just 2 hosts, handle file deletions and can detect conflicts. It is expedient for HA-clusters, HPC-clusters, COWs and server farms. %prep %setup %{?suse_update_config:%{suse_update_config}} %build export CFLAGS="$RPM_OPT_FLAGS -I/usr/kerberos/include" if ! [ -f configure ]; then ./autogen.sh; fi %configure --enable-mysql --enable-postgres --enable-sqlite3 make all %install [ "$RPM_BUILD_ROOT" != "/" ] && [ -d $RPM_BUILD_ROOT ] && rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT%{_sbindir} mkdir -p $RPM_BUILD_ROOT%{_var}/lib/csync2 mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/xinetd.d %makeinstall install -m 644 csync2.xinetd $RPM_BUILD_ROOT%{_sysconfdir}/xinetd.d/csync2 install -m 644 doc/csync2_paper.pdf $RPM_BUILD_ROOT%{_docdir}/csync2/csync2_paper.pdf %clean [ "$RPM_BUILD_ROOT" != "/" ] && [ -d $RPM_BUILD_ROOT ] && rm -rf $RPM_BUILD_ROOT make clean %post if ! grep -q "^csync2" %{_sysconfdir}/services ; then echo "csync2 30865/tcp" >>%{_sysconfdir}/services fi %files %defattr(-,root,root) %{_sbindir}/csync2 %{_sbindir}/csync2-compare %{_var}/lib/csync2 %doc %{_mandir}/man1/csync2.1.gz %doc %{_docdir}/csync2/csync2_paper.pdf %doc %{_docdir}/csync2/ChangeLog %doc %{_docdir}/csync2/README %doc %{_docdir}/csync2/AUTHORS %config(noreplace) %{_sysconfdir}/xinetd.d/csync2 %config(noreplace) %{_sysconfdir}/csync2.cfg %changelog * Tue Jan 27 2015 Lars Ellenberg - 2.0-1 - New upstream release csync2-2.0-22-gce67c55/csync2.xinetd000066400000000000000000000004141333746731400166530ustar00rootroot00000000000000# default: on # description: csync2 service csync2 { flags = REUSE socket_type = stream wait = no user = root group = root server = /usr/sbin/csync2 server_args = -i -l #log_on_failure += USERID disable = no # only_from = 192.168.199.3 192.168.199.4 } csync2-2.0-22-gce67c55/csync2_locheck.sh000066400000000000000000000024141333746731400174640ustar00rootroot00000000000000#!/bin/sh # # Example /root/.bash_logout file for csync2. # # csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # csync2 -cr / if csync2 -M; then echo "!!" echo "!! There are unsynced changes! Type 'yes' if you still want to" echo "!! exit (or press crtl-c) and anything else if you want to start" echo "!! a new login shell instead." echo "!!" if read -p "Do you really want to logout? " in && [ ".$in" != ".yes" ]; then exec bash --login fi fi csync2-2.0-22-gce67c55/cygwin/000077500000000000000000000000001333746731400155365ustar00rootroot00000000000000csync2-2.0-22-gce67c55/cygwin/buildit.sh000077500000000000000000000055721333746731400175420ustar00rootroot00000000000000#!/bin/bash -ex # # csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA TRGDIR=/cygdrive/c/csync2 if ! [ -f sqlite-2.8.16.tar.gz ]; then wget http://www.sqlite.org/sqlite-2.8.16.tar.gz -O sqlite-2.8.16.tar.gz fi if ! [ -f librsync-0.9.7.tar.gz ]; then wget http://mesh.dl.sourceforge.net/sourceforge/librsync/librsync-0.9.7.tar.gz -O librsync-0.9.7.tar.gz fi cd .. mkdir -p $TRGDIR rm -f $TRGDIR/*.exe $TRGDIR/*.dll if ! [ -f config.h ]; then ./configure \ --with-librsync-source=cygwin/librsync-0.9.7.tar.gz \ --with-libsqlite-source=cygwin/sqlite-2.8.16.tar.gz \ --disable-gnutls --sysconfdir=$TRGDIR fi make private_librsync make private_libsqlite make CFLAGS='-DREAL_DBDIR=\".\"' ignore_dlls="KERNEL32.dll|USER32.dll|GDI32.dll|mscoree.dll" copy_dlls() { for dll in $( strings "$@" | egrep '^[^ ]+\.dll$' | sort -u; ) do if echo "$dll" | egrep -qv "^($ignore_dlls)\$" then cp -v /bin/$dll $TRGDIR/$dll ignore_dlls="$ignore_dlls|$dll" copy_dlls $TRGDIR/$dll fi done } cp -v csync2.exe $TRGDIR/csync2.exe cp -v sqlite-2.8.16/sqlite.exe $TRGDIR/sqlite.exe cp -v /bin/killall.exe /bin/cp.exe /bin/ls.exe /bin/wc.exe $TRGDIR/ cp -v /bin/find.exe /bin/xargs.exe /bin/rsync.exe $TRGDIR/ cp -v /bin/grep.exe /bin/gawk.exe /bin/wget.exe $TRGDIR/ cp -v /bin/rxvt.exe /bin/unzip.exe /bin/libW11.dll $TRGDIR/ cp -v /bin/diff.exe /bin/date.exe /bin/tail.exe $TRGDIR/ cp -v /bin/head.exe /bin/sleep.exe /bin/rm.exe $TRGDIR/ cp -v /bin/bash.exe $TRGDIR/sh.exe copy_dlls $TRGDIR/*.exe $TRGDIR/*.dll cd cygwin PATH="$PATH:/cygdrive/c/WINNT/Microsoft.NET/Framework/v1.0.3705" csc /nologo cs2hintd_fseh.cs gcc -Wall cs2monitor.c -o cs2monitor.exe -DTRGDIR="\"$TRGDIR"\" -{I,L}../sqlite-2.8.16 -lprivatesqlite gcc -Wall ../urlencode.o cs2hintd.c -o cs2hintd.exe -{I,L}../sqlite-2.8.16 -lprivatesqlite cp -v readme_pkg.txt $TRGDIR/README.txt cp -v ../README $TRGDIR/README-csync2.txt cp -v cs2hintd_fseh.exe cs2hintd.exe cs2monitor.exe $TRGDIR/ cd $( dirname $TRGDIR/ ) rm -f $( basename $TRGDIR ).zip zip -r $( basename $TRGDIR ).zip $( basename $TRGDIR ) \ -i '*.txt' '*.dll' '*.exe' echo "DONE." csync2-2.0-22-gce67c55/cygwin/cs2hintd.c000066400000000000000000000060611333746731400174230ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include static sqlite *db = 0; extern const char *url_encode(const char * in); void sql(const char *fmt, ...) { char *sql; va_list ap; int rc, busyc = 0; va_start(ap, fmt); vasprintf(&sql, fmt, ap); va_end(ap); while (1) { rc = sqlite_exec(db, sql, 0, 0, 0); if ( rc != SQLITE_BUSY ) break; if (busyc++ > 60) { fprintf(stderr, "** Database busy for long time [%d secs]: %s\n", busyc, sql); } sleep(1); } if ( rc != SQLITE_OK ) { fprintf(stderr, "** Database Error %d in '%s'!\n", rc, sql); sqlite_exec(db, "COMMIT TRANSACTION", 0, 0, 0); exit(1); } free(sql); } int main(int argc, char **argv) { FILE *fseh; char line[4096], *c; int in_transaction = 0; if (argc < 3) { fprintf(stderr, "Usage: %s dbfile directory [ directory [ ... ] ]\n", argv[0]); exit(1); } db = sqlite_open(argv[1], 0, 0); if (!db) { fprintf(stderr, "** Can't open database file: %s\n", argv[1]); exit(1); } { int i, pos = 0; int command_len = 100; char *command; for (i=2; i 0) WriteFilename(); } } } private static void WriteFilename() { Hashtable donethat = new Hashtable(); while (changed_files.Count > 0) { string filename = (string)changed_files.Dequeue(); if (donethat.ContainsKey(filename)) continue; donethat.Add(filename, 1); filename = Regex.Replace(filename, "^([a-zA-Z]):\\\\", "/cygdrive/$1/" ); filename = Regex.Replace(filename, "\\\\", "/" ); Console.WriteLine("+ {0}", filename); } Console.WriteLine("- COMMIT"); } private static void ScheduleWriteFilename(string filename) { changed_files.Enqueue(filename); } private static void OnChanged(object source, FileSystemEventArgs e) { Console.Error.WriteLine("** FS Event: '{0}' {1}.", e.FullPath, e.ChangeType); ScheduleWriteFilename(e.FullPath); } private static void OnRenamed(object source, RenamedEventArgs e) { Console.Error.WriteLine("** FS Event: '{0}' renamed to '{1}'.", e.OldFullPath, e.FullPath); ScheduleWriteFilename(e.OldFullPath); ScheduleWriteFilename(e.FullPath); } } csync2-2.0-22-gce67c55/cygwin/cs2monitor.c000066400000000000000000000303441333746731400200050ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include static sqlite *db = 0; static int db_busyc = 0; static int last_busyc_warn = 0; static int non_blocking_mode = 0; static char myhostname[256]; static char *dbname; static char *dirname0 = 0; static char *dirname1 = 0; static char *dirname2 = 0; static char *dirname3 = 0; static char *dirname4 = 0; static char *dirname5 = 0; static char *dirname6 = 0; static char *dirname7 = 0; static time_t restart_time; static int wheel_counter; struct service { char *name; void (*exec_func)(); time_t timestamp; int do_restart; int do_panic; int pid; }; static void exec_tcp_listener() { if (non_blocking_mode) execl("./csync2.exe", "csync2.exe", "-Biiv", NULL); else execl("./csync2.exe", "csync2.exe", "-iiv", NULL); } static void exec_init_checker() { if (non_blocking_mode) execl("./csync2.exe", "csync2.exe", "-Bcrv", "/", NULL); else execl("./csync2.exe", "csync2.exe", "-crv", "/", NULL); } static void exec_hint_checker() { if (non_blocking_mode) execl("./csync2.exe", "csync2.exe", "-Bcv", NULL); else execl("./csync2.exe", "csync2.exe", "-Bcv", NULL); } static void exec_checker() { if (non_blocking_mode) execl("./csync2.exe", "csync2.exe", "-Bcvr", "/", NULL); else execl("./csync2.exe", "csync2.exe", "-Bcvr", "/", NULL); } static void exec_updater() { if (non_blocking_mode) execl("./csync2.exe", "csync2.exe", "-Buv", NULL); else execl("./csync2.exe", "csync2.exe", "-uv", NULL); } static void exec_hintd() { execl("./cs2hintd.exe", "cs2hintd.exe", dbname, dirname0, dirname1, dirname2, dirname3, dirname4, dirname5, dirname6, dirname7, NULL); } static struct service service_tcp_listener = { "TCP Listener", exec_tcp_listener, 0, 1, 0, 0 }; static struct service service_init_checker = { "Initial Full Checker", exec_init_checker, 0, 0, 0, 0 }; static struct service service_hint_checker = { "Incremenmtal Checker", exec_hint_checker, 0, 5, 0, 0 }; static struct service service_checker = { "Full Checker", exec_checker, 0, 300, 0, 0 }; static struct service service_updater = { "Incremenmtal Updater", exec_updater, 0, 5, 0, 0 }; static struct service service_hintd = { "Filesystem Watcher", exec_hintd, 0, 0, 1, 0 }; static struct service *services_with_hintd[] = { &service_tcp_listener, &service_init_checker, &service_hint_checker, &service_updater, &service_hintd, NULL }; static struct service *services_without_hintd[] = { &service_tcp_listener, &service_checker, &service_updater, NULL }; static struct service **services; static int got_ctrl_c = 0; static void ctrl_c_handler(int signum) { got_ctrl_c = 1; } static int my_system(const char *command) { int pid, status; if ((pid = fork()) == 0) { execl("sh", "sh", "-c", command, NULL); _exit(100); } waitpid(pid, &status, 0); if (!WIFEXITED(status)) fprintf(stderr, "CS2MONITOT: Error while executing '%s': " "Anormal exit.\n", command); else if (WEXITSTATUS(status) && WEXITSTATUS(status) != 1) fprintf(stderr, "CS2MONITOT: Error while executing '%s': " "Returncode is %d.\n", command, WEXITSTATUS(status)); return WIFEXITED(status) ? WEXITSTATUS(status) : -1; } int main(int argc, char **argv) { signal(SIGINT, ctrl_c_handler); signal(SIGTERM, ctrl_c_handler); gethostname(myhostname, 256); myhostname[255] = 0; asprintf(&dbname, "%s.db", myhostname); if (argc >= 3 && !strcmp(argv[1], "-B")) { non_blocking_mode = 1; if (strcmp(argv[2], "-")) { dirname0 = argc >= 3 ? argv[2] : 0; dirname1 = argc >= 4 ? argv[3] : 0; dirname2 = argc >= 5 ? argv[4] : 0; dirname3 = argc >= 6 ? argv[5] : 0; dirname4 = argc >= 7 ? argv[6] : 0; dirname5 = argc >= 8 ? argv[7] : 0; dirname6 = argc >= 9 ? argv[8] : 0; dirname7 = argc >= 10 ? argv[9] : 0; } } else if (argc >= 2) { if (strcmp(argv[1], "-")) { dirname0 = argc >= 2 ? argv[1] : 0; dirname1 = argc >= 3 ? argv[2] : 0; dirname2 = argc >= 4 ? argv[3] : 0; dirname3 = argc >= 5 ? argv[4] : 0; dirname4 = argc >= 6 ? argv[5] : 0; dirname5 = argc >= 7 ? argv[6] : 0; dirname6 = argc >= 8 ? argv[7] : 0; dirname7 = argc >= 9 ? argv[8] : 0; } } else { fprintf(stderr, "Usage: %s [-B] [datadir [..] | -]\n", argv[0]); return 1; } services = dirname0 ? services_with_hintd : services_without_hintd; { int p[2]; pipe(p); if (!fork()) { char *buffer[100], *timer, ch; int i, pos=0, epos=0; time_t last_update = 0; dup2(p[0], 0); close(p[0]); close(p[1]); for (i=0; i<100; i++) buffer[i] = 0; buffer[99] = malloc(256); buffer[99][0] = 0; timer = strdup(""); while (read(0, &ch, 1) == 1) { write(1, &ch, 1); switch (ch) { case '\n': if (buffer[0]) free(buffer[0]); for (i=1; i<100; i++) buffer[i-1] = buffer[i]; buffer[99] = malloc(256); strcpy(buffer[99], timer); epos = strlen(timer); write(1, timer, epos); write(1, "\r", 1); pos = 0; break; case '\r': if (buffer[99][0] == '[') { free(timer); timer = strdup(buffer[99]); } pos = 0; break; default: if (pos < 255) { if (++pos > epos) epos = pos; buffer[99][pos-1] = ch; buffer[99][epos] = 0; } } if (last_update+2 < time(0) && (ch == '\n' || ch == '\r')) { FILE *f = fopen("cs2monitor.log", "w"); if (f) { for (i=0; i<100; i++) { if (buffer[i]) fprintf(f, "%s\r\n", buffer[i]); } if (strcmp(buffer[99], timer)) fprintf(f, "%s\r\n", timer); fclose(f); } else fprintf(stderr, "CS2MONITOR: Can't update cs2monitor.log!\n"); last_update = time(0); } } return 0; } dup2(p[1], 1); dup2(p[1], 2); close(p[0]); close(p[1]); printf("CS2MONITOR: Writing log to cs2monitor.log.\n"); } printf("\n"); printf("**********************************************************\n"); printf("\n"); printf("Csync2 Monitor\n"); printf("\n"); printf("Basedir: %s\n", TRGDIR); printf("Hostname: %s\n", myhostname); printf("Database: %s\n", dbname); printf("\n"); if (!dirname0) printf("No Windows FS-Watcher Helper.\n"); if (dirname0) printf("Datadir #0: %s\n", dirname0); if (dirname1) printf("Datadir #1: %s\n", dirname1); if (dirname2) printf("Datadir #2: %s\n", dirname2); if (dirname3) printf("Datadir #3: %s\n", dirname3); if (dirname4) printf("Datadir #4: %s\n", dirname4); if (dirname5) printf("Datadir #5: %s\n", dirname5); if (dirname6) printf("Datadir #6: %s\n", dirname6); if (dirname7) printf("Datadir #7: %s\n", dirname7); printf("\n"); printf("**********************************************************\n"); printf("\n"); fflush(stdout); if (chdir(TRGDIR) < 0) goto io_error; printf("CS2MONITOR: Killing all running 'csync2' processes...\n"); my_system("./killall.exe csync2"); printf("CS2MONITOR: Killing all running 'cs2hintd' processes...\n"); my_system("./killall.exe cs2hintd"); printf("CS2MONITOR: Killing all running 'cs2hintd_fseh' processes...\n"); my_system("./killall.exe cs2hintd_fseh"); if (0) { struct service **s; restart_entry_point: for (s = services; *s; s++) (*s)->pid = 0; } fflush(stdout); sleep(1); { char vacuum_command[strlen(dbname) + 100]; sprintf(vacuum_command, "./sqlite.exe %s vacuum", dbname); printf("CS2MONITOR: Running database VACUUM command...\n"); my_system(vacuum_command); printf("CS2MONITOR: Cleaning up old out-of-config DB records...\n"); my_system("./csync2.exe -Rv"); } { unsigned char random_number = 0; int rand = open("/dev/urandom", O_RDONLY); read(rand, &random_number, sizeof(unsigned char)); close(rand); restart_time = 60 + random_number%30; printf("CS2MONITOR: Automatic restart in %d minutes.\n", (int)restart_time); restart_time = time(0) + restart_time * 60; } while (1) { time_t remaining_restart_time; struct service **s; int rc; for (s = services; *s; s++) { /* never has been started before */ if ((*s)->pid == 0) { printf("CS2MONITOR: [%ld] Running job '%s' ...\n", (long)time(0), (*s)->name); fflush(stdout); if (((*s)->pid = fork()) == 0) { (*s)->exec_func(); goto io_error; } } else /* has been terminated */ if ((*s)->pid == -1) { if ((*s)->do_restart && time(0) > ((*s)->timestamp + (*s)->do_restart)) { printf("CS2MONITOR: [%ld] Running job '%s' ...\n", (long)time(0), (*s)->name); fflush(stdout); if (((*s)->pid = fork()) == 0) { (*s)->exec_func(); goto io_error; } } } else /* is running or a zombie */ { int waitpid_status, waitpid_rc; waitpid_rc = waitpid((*s)->pid, &waitpid_status, WNOHANG); if (waitpid_rc != 0) { (*s)->pid = -1; (*s)->timestamp = time(0); if ((*s)->do_panic) { printf("CS2MONITOR: Job '%s' terminated! Restarting CS2MONITOR..\n", (*s)->name); goto panic_restart_everything; } } } } { time_t t1 = time(0), t2; int i = 0; do { i++; sleep(1); t2 = time(0); } while (t1 == t2); wheel_counter = (wheel_counter+1) % 16; remaining_restart_time = restart_time - time(0); printf("[%02d:%02d] (%c) %.*s\r", (int)(remaining_restart_time / 60), (int)(remaining_restart_time % 60), "/-\\|/-\\|:.,.:`:|"[wheel_counter], i, i > 1 ? ".........." : ""); fflush(stdout); } if (remaining_restart_time <= 0) { printf("CS2MONITOR: Restarting CS2MONITOR now...\n"); goto panic_restart_everything; } if (got_ctrl_c) { printf("CS2MONITOR: Got Ctrl-C signal. Terminating...\n"); goto panic_restart_everything; } db = sqlite_open(dbname, 0, 0); if (!db) { printf("CS2MONITOR: Can't open database file! Restarting CS2MONITOR..\n"); goto panic_restart_everything; } rc = sqlite_exec(db, "BEGIN TRANSACTION", 0, 0, 0); if ( rc != SQLITE_BUSY && rc != SQLITE_OK ) { printf("CS2MONITOR: Got database error %d on DB check! Restarting CS2MONITOR..\n", rc); sqlite_close(db); goto panic_restart_everything; } if ( rc == SQLITE_BUSY ) { db_busyc++; if (db_busyc > 600) { printf("CS2MONITOR: Database is busy for 600 seconds! Restarting CS2MONITOR..\n"); sqlite_close(db); goto panic_restart_everything; } if (db_busyc > 300 || db_busyc > last_busyc_warn + 10) { printf("CS2MONITOR: DB is busy for %d seconds now (Monitor restart at 600 seconds).\n", db_busyc); sqlite_close(db); last_busyc_warn = db_busyc; } } else { sqlite_exec(db, "COMMIT TRANSACTION", 0, 0, 0); db_busyc = last_busyc_warn = 0; } sqlite_close(db); } panic_restart_everything: fflush(stdout); sleep(1); printf("CS2MONITOR: Killing all running 'csync2' processes...\n"); my_system("./killall.exe csync2"); printf("CS2MONITOR: Killing all running 'cs2hintd' processes...\n"); my_system("./killall.exe cs2hintd"); printf("CS2MONITOR: Killing all running 'cs2hintd_fseh' processes...\n"); my_system("./killall.exe cs2hintd_fseh"); fflush(stdout); sleep(5); while (waitpid(-1, 0, WNOHANG) > 0) {}; if (got_ctrl_c) { printf("CS2MONITOR: Bye.\n"); fflush(stdout); sleep(1); return 0; } printf("CS2MONITOR: Restarting...\n"); fflush(stdout); sleep(1); goto restart_entry_point; io_error: fprintf(stderr, "CS2MONITOR I/O Error: %s\n", strerror(errno)); return 1; } csync2-2.0-22-gce67c55/cygwin/perftest.pl000077500000000000000000000010451333746731400177320ustar00rootroot00000000000000#!/usr/bin/perl # # Simple performance testing utility for csync2 on cygwin.. $| = 1; my $basedir = "/cygdrive/f/sharedata"; my $filenumber = 100000; if ($ARGV[0] eq "create") { for my $i (0..$filenumber) { my $f = crypt($i, "00"); $f =~ s:[^a-zA-Z0-9]:_:g; $f =~ s:^..:$basedir/:; open(F, ">$f") or die $!; print F "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 $f\n" x 100; close F; if ( $i < $filenumber ) { printf("%s%5d ", $i ? "\n" : "", $i) if $i % 10000 == 0; print "." if $i % 200 == 0; } else { print "\n"; } } } csync2-2.0-22-gce67c55/cygwin/readme_pkg.txt000066400000000000000000000034161333746731400204010ustar00rootroot00000000000000 Csync2 for Win32 (cygwin) ========================= The Csync2 homepage can be reached at http://oss.linbit.com/. Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH http://www.linbit.com; see also AUTHORS This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The "Csync2 hint daemon for win32" needs a .NET runtime. It can be downloaded from the microsoft webpage: http://www.microsoft.com/downloads/details.aspx?FamilyID=d7158dee-a83f-4e21-b05a-009d06457787&displaylang=en "Csync2 for Win32 (cygwin)" is based on the free software libraries Librsync, SQLite, SQLite.NET wrapper, OpenSSL and Cygwin. Setup: ====== 1. Extract the contents of this .ZIP archive to c:\csync2. 2. Create a c:\tmp directory. 3. Create a c:\csync2\csync2.cfg (or copy it from existing nodes). 4. Add entries to c:\winnt\system32\drivers\etc\hosts (optional). 5. Run c:\csync2\monitor.exe in a command window. Pass the directory names which should be monitored by the hint deamon(s) as parameters to "monitor.exe". Read the csync2 documentation (bundled with the csync2 source tar) for more information about writing csync2 configuration files. Additional Tools ================ Some basic programs from the cygwin toolchain which might be of great help for bootstrapping large csync2 setups (such as a unix-like 'cp' programm) are also included in this "Csync2 for Win32 (cygwin)" binary distribution. An SQLite command line shell is included as well. This is especially useful for administratice tasks such as running the SQL VACUUM command. csync2-2.0-22-gce67c55/daemon.c000066400000000000000000000527221333746731400156550ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __CYGWIN__ #include #endif static const char *cmd_error; int csync_setBackupFileStatus(char *filename, int backupDirLength); int csync_unlink(const char *filename, int ign) { struct stat st; int rc; if ( lstat_strict(prefixsubst(filename), &st) != 0 ) return 0; if ( ign==2 && S_ISREG(st.st_mode) ) return 0; rc = S_ISDIR(st.st_mode) ? rmdir(prefixsubst(filename)) : unlink(prefixsubst(filename)); if ( rc && !ign ) cmd_error = strerror(errno); return rc; } int csync_check_dirty(const char *filename, const char *peername, int isflush) { int rc = 0; csync_check(filename, 0, 0); if (isflush) return 0; SQL_BEGIN("Check if file is dirty", "SELECT 1 FROM dirty WHERE filename = '%s' LIMIT 1", url_encode(filename)) { rc = 1; cmd_error = conn_response(CR_ERR_ALSO_DIRTY_HERE); } SQL_END; if (rc && peername) csync_mark(filename, peername, 0); return rc; } void csync_file_update(const char *filename, const char *peername) { struct stat st; SQL("Removing file from dirty db", "delete from dirty where filename = '%s' and peername = '%s'", url_encode(filename), peername); if ( lstat_strict(prefixsubst(filename), &st) != 0 || csync_check_pure(filename) ) { SQL("Removing file from file db", "delete from file where filename = '%s'", url_encode(filename)); } else { const char *checktxt = csync_genchecktxt(&st, filename, 0); SQL("Deleting old record from file db", "DELETE FROM file WHERE filename = '%s'", url_encode(filename)); SQL("Insert record to file db", "INSERT INTO file (filename, checktxt) values " "('%s', '%s')", url_encode(filename), url_encode(checktxt)); } } void csync_file_flush(const char *filename) { SQL("Removing file from dirty db", "delete from dirty where filename ='%s'", url_encode(filename)); } int csync_file_backup(const char *filepath) { static char error_buffer[1024]; const struct csync_group *g = NULL; struct stat buf; int rc; /* ============================================================================================== * As of now, filepath may only contain prefixes but we may need to resolve other * dynamic references like environment variables, symbolic links, etc in future * if we plan to support those in later releases *==============================================================================================*/ const char *filename = prefixsubst(filepath); int filename_len = strlen(filename); //can filename be null? while ((g = csync_find_next(g, filepath))) { if (g->backup_directory && g->backup_generations > 1) { unsigned int bak_dir_len = strlen(g->backup_directory); char backup_filename[bak_dir_len + filename_len + 12]; char backup_otherfilename[bak_dir_len + filename_len + 12]; int fd_in, fd_out; unsigned int i, lastSlash = 0; mode_t mode; csync_debug(1, "backup %s for group %s\n", filename, g->gname); if (stat(g->backup_directory, &buf) != 0) { csync_debug(3, "backup directory configured is not present, so creating it"); if (mkpath(g->backup_directory, 0700)) { csync_debug(1, "ERROR : unable to create backup directory %s ; backup failed \n", g->backup_directory); return 1; } } else if (!S_ISDIR(buf.st_mode)) { csync_debug(1, "ERROR : location configured for backup %s is not a directory; backup failed \n", g->backup_directory); return 1; } // Skip generation of directories rc = stat(filename, &buf); if (S_ISDIR(buf.st_mode)) { csync_debug(1, "directory. Skip generation \n"); return 0; } fd_in = open(filename, O_RDONLY); if (fd_in < 0) return 0; memcpy(backup_filename, g->backup_directory, bak_dir_len); backup_filename[bak_dir_len] = 0; mode = 0700; /* open coded strrchr?? why? */ for (i = filename_len; i > 0; i--) if (filename[i] == '/') { lastSlash = i; break; } for (i = 0; i < filename_len; i++) { // Create directories in filename // TODO: Get the mode from the orig. dir if (filename[i] == '/' && i <= lastSlash) { backup_filename[bak_dir_len + i] = 0; csync_debug(1, "mkdir %s \n", backup_filename); mkdir(backup_filename, mode); // Dont check the empty string. if (i != 0) csync_setBackupFileStatus(backup_filename, bak_dir_len); } backup_filename[bak_dir_len + i] = filename[i]; } backup_filename[bak_dir_len + filename_len] = 0; backup_filename[bak_dir_len] = '/'; memcpy(backup_otherfilename, backup_filename, bak_dir_len + filename_len); for (i = g->backup_generations - 1; i; i--) { if (i != 1) snprintf(backup_filename + bak_dir_len + filename_len, 12, ".%u", i - 1); backup_filename[bak_dir_len + filename_len] = '\0'; snprintf(backup_otherfilename + bak_dir_len + filename_len, 12, ".%u", i); rc = rename(backup_filename, backup_otherfilename); csync_debug(1, "renaming backup files '%s' to '%s'. rc = %d\n", backup_filename, backup_otherfilename, rc); } /* strcpy(backup_filename+bak_dir_len+filename_len, ""); */ fd_out = open(backup_filename, O_WRONLY | O_CREAT, 0600); if (fd_out < 0) { snprintf(error_buffer, 1024, "Open error while backing up '%s': %s\n", filename, strerror(errno)); cmd_error = error_buffer; close(fd_in); return 1; } csync_debug(1, "Copying data from %s to backup file %s \n", filename, backup_filename); rc = csync_copy_file(fd_in, fd_out); if (rc != 0) { csync_debug(1, "csync_backup error 2\n"); snprintf(error_buffer, 1024, "Write error while backing up '%s': %s\n", filename, strerror(errno)); cmd_error = error_buffer; // TODO verify file disapeared ? // // return 1; } csync_setBackupFileStatus(backup_filename, bak_dir_len); } } return 0; } int csync_copy_file(int fd_in, int fd_out) { char buffer[512]; int read_len = read(fd_in, buffer, 512); while (read_len > 0) { int write_len = 0; while (write_len < read_len) { int rc = write(fd_out, buffer + write_len, read_len - write_len); if (rc == -1) { close(fd_in); close(fd_out); //TODO verify return code. return errno; } write_len += rc; } read_len = read(fd_in, buffer, 512); } close(fd_in); close(fd_out); return 0; } /* get the mode from the orig directory. Looking from the back_dir_len should produce the original dir. */ int csync_setBackupFileStatus(char *filename, int backupDirLength) { struct stat buf; int rc = stat((filename + backupDirLength), &buf); if (rc == 0) { csync_debug(1, "Stating original file %s rc: %d mode: %o \n", (filename + backupDirLength), rc, buf.st_mode); rc = chown(filename, buf.st_uid, buf.st_gid); csync_debug(rc == 0, "Changing owner of %s to user %d and group %d, rc= %d \n", filename, buf.st_uid, buf.st_gid, rc); rc = chmod(filename, buf.st_mode); csync_debug(rc == 0, "Changing mode of %s to mode %o, rc= %d \n", filename, buf.st_mode, rc); } else { csync_debug(0, "Error getting mode and owner ship from %s \n", (filename + backupDirLength)); return -1; } return 0; }; struct csync_command { char *text; int check_perm; int check_dirty; int unlink; int update; int need_ident; int action; }; enum { A_SIG, A_FLUSH, A_MARK, A_TYPE, A_GETTM, A_GETSZ, A_DEL, A_PATCH, A_MKDIR, A_MKCHR, A_MKBLK, A_MKFIFO, A_MKLINK, A_MKSOCK, A_SETOWN, A_SETMOD, A_SETIME, A_LIST, A_GROUP, A_DEBUG, A_HELLO, A_BYE }; struct csync_command cmdtab[] = { { "sig", 1, 0, 0, 0, 1, A_SIG }, { "mark", 1, 0, 0, 0, 1, A_MARK }, { "type", 2, 0, 0, 0, 1, A_TYPE }, { "gettm", 1, 0, 0, 0, 1, A_GETTM }, { "getsz", 1, 0, 0, 0, 1, A_GETSZ }, { "flush", 1, 1, 0, 0, 1, A_FLUSH }, { "del", 1, 1, 0, 1, 1, A_DEL }, { "patch", 1, 1, 2, 1, 1, A_PATCH }, { "mkdir", 1, 1, 1, 1, 1, A_MKDIR }, { "mkchr", 1, 1, 1, 1, 1, A_MKCHR }, { "mkblk", 1, 1, 1, 1, 1, A_MKBLK }, { "mkfifo", 1, 1, 1, 1, 1, A_MKFIFO }, { "mklink", 1, 1, 1, 1, 1, A_MKLINK }, { "mksock", 1, 1, 1, 1, 1, A_MKSOCK }, { "setown", 1, 1, 0, 2, 1, A_SETOWN }, { "setmod", 1, 1, 0, 2, 1, A_SETMOD }, { "setime", 1, 0, 0, 2, 1, A_SETIME }, { "list", 0, 0, 0, 0, 1, A_LIST }, #if 0 { "debug", 0, 0, 0, 0, 1, A_DEBUG }, #endif { "group", 0, 0, 0, 0, 0, A_GROUP }, { "hello", 0, 0, 0, 0, 0, A_HELLO }, { "bye", 0, 0, 0, 0, 0, A_BYE }, { 0, 0, 0, 0, 0, 0, 0 } }; typedef union address { struct sockaddr sa; struct sockaddr_in sa_in; struct sockaddr_in6 sa_in6; struct sockaddr_storage ss; } address_t; const char *csync_inet_ntop(address_t *addr) { char buf[INET6_ADDRSTRLEN]; sa_family_t af = addr->sa.sa_family; return inet_ntop(af, af == AF_INET ? (void*)&addr->sa_in.sin_addr : af == AF_INET6 ? (void*)&addr->sa_in6.sin6_addr : NULL, buf, sizeof(buf)); } /* * Loops (to cater for multihomed peers) through the address list returned by * gethostbyname(), returns 1 if any match with the address obtained from * getpeername() during session startup. * Otherwise returns 0 (-> identification failed). * * TODO switch to a getnameinfo in conn_open. * TODO add a "pre-authenticated" pipe mode for use over ssh */ int verify_peername(const char *name, address_t *peeraddr) { sa_family_t af = peeraddr->sa.sa_family; struct addrinfo hints; struct addrinfo *result, *rp; int try_mapped_ipv4; int s; /* Obtain address(es) matching host */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* Datagram socket */ s = getaddrinfo(name, NULL, &hints, &result); if (s != 0) { csync_debug(1, "getaddrinfo: %s\n", gai_strerror(s)); return 0; } try_mapped_ipv4 = af == AF_INET6 && !memcmp(&peeraddr->sa_in6.sin6_addr, "\0\0\0\0" "\0\0\0\0" "\0\0\xff\xff", 12); /* getaddrinfo() returns a list of address structures. * Try each address. */ for (rp = result; rp != NULL; rp = rp->ai_next) { /* both IPv4 */ if (af == AF_INET && rp->ai_family == AF_INET && !memcmp(&((struct sockaddr_in*)rp->ai_addr)->sin_addr, &peeraddr->sa_in.sin_addr, sizeof(struct in_addr))) break; /* both IPv6 */ if (af == AF_INET6 && rp->ai_family == AF_INET6 && !memcmp(&((struct sockaddr_in6*)rp->ai_addr)->sin6_addr, &peeraddr->sa_in6.sin6_addr, sizeof(struct in6_addr))) break; /* peeraddr IPv6, but actually ::ffff:I.P.v.4, * and forward lookup returned IPv4 only */ if (af == AF_INET6 && rp->ai_family == AF_INET && try_mapped_ipv4 && !memcmp(&((struct sockaddr_in*)rp->ai_addr)->sin_addr, (unsigned char*)&peeraddr->sa_in6.sin6_addr + 12, sizeof(struct in_addr))) break; } freeaddrinfo(result); if (rp != NULL) /* memcmp found a match */ return conn_check_peer_cert(name, 0); return 0; } /* Why do all this fuzz, and not simply --assume-authenticated? * To limit the impact of an accidental misconfiguration. */ void set_peername_from_env(address_t *p, const char *env) { struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, }; struct addrinfo *result; char *c; int s; char *val = getenv(env); csync_debug(3, "getenv(%s): >>%s<<\n", env, val ?: ""); if (!val) return; val = strdup(val); if (!val) return; c = strchr(val, ' '); if (!c) return; *c = '\0'; s = getaddrinfo(val, NULL, &hints, &result); if (s != 0) { csync_debug(1, "getaddrinfo: %s\n", gai_strerror(s)); return; } /* getaddrinfo() may return a list of address structures. * Use the first one. */ if (result) memcpy(p, result->ai_addr, result->ai_addrlen); freeaddrinfo(result); } static int setup_tag(char *tag[32], char *line) { int i = 0; char *context; tag[0] = strtok_r(line, "\t \r\n", &context); while (tag[i] && i < 31) { tag[++i] = strtok_r(NULL, "\t \r\n", &context); } while (i < 32) { tag[i++] = ""; } if (!tag[0][0]) { return 0; } for (i = 0; i < 32; i++) { tag[i] = strdup(url_decode(tag[i])); } return 1; } static void destroy_tag(char *tag[32]) { int i = 0; for (i = 0; i < 32; i++) free(tag[i]); } void csync_daemon_session() { static char line[4 * 4096]; struct stat sb; address_t peername = { .sa.sa_family = AF_UNSPEC, }; socklen_t peerlen = sizeof(peername); char *peer=0, *tag[32]; int i; if (fstat(0, &sb)) csync_fatal("Can't run fstat on fd 0: %s", strerror(errno)); switch (sb.st_mode & S_IFMT) { case S_IFSOCK: if ( getpeername(0, &peername.sa, &peerlen) == -1 ) csync_fatal("Can't run getpeername on fd 0: %s", strerror(errno)); break; case S_IFIFO: set_peername_from_env(&peername, "SSH_CLIENT"); break; /* fall through */ default: csync_fatal("I'm only talking to sockets or pipes! %x\n", sb.st_mode & S_IFMT); break; } while ( conn_gets(line, sizeof(line)) ) { int cmdnr; if (!setup_tag(tag, line)) continue; for (cmdnr=0; cmdtab[cmdnr].text; cmdnr++) if ( !strcasecmp(cmdtab[cmdnr].text, tag[0]) ) break; if ( !cmdtab[cmdnr].text ) { cmd_error = conn_response(CR_ERR_UNKNOWN_COMMAND); goto abort_cmd; } cmd_error = 0; if ( cmdtab[cmdnr].need_ident && !peer ) { conn_printf("Dear %s, please identify first.\n", csync_inet_ntop(&peername) ?: "stranger"); goto next_cmd; } if ( cmdtab[cmdnr].check_perm ) on_cygwin_lowercase(tag[2]); if ( cmdtab[cmdnr].check_perm ) { if ( cmdtab[cmdnr].check_perm == 2 ) csync_compare_mode = 1; int perm = csync_perm(tag[2], tag[1], peer); if ( cmdtab[cmdnr].check_perm == 2 ) csync_compare_mode = 0; if ( perm ) { if ( perm == 2 ) { csync_mark(tag[2], peer, 0); cmd_error = conn_response(CR_ERR_PERM_DENIED_FOR_SLAVE); } else cmd_error = conn_response(CR_ERR_PERM_DENIED); goto abort_cmd; } } if ( cmdtab[cmdnr].check_dirty && csync_check_dirty(tag[2], peer, cmdtab[cmdnr].action == A_FLUSH) ) goto abort_cmd; if ( cmdtab[cmdnr].unlink ) csync_unlink(tag[2], cmdtab[cmdnr].unlink); switch ( cmdtab[cmdnr].action ) { case A_SIG: { struct stat st; if ( lstat_strict(prefixsubst(tag[2]), &st) != 0 ) { if ( errno == ENOENT ) { struct stat sb; char parent_dirname[strlen(tag[2])]; split_dirname_basename(parent_dirname, NULL, tag[2]); if ( lstat_strict(prefixsubst(parent_dirname), &sb) != 0 ) cmd_error = conn_response(CR_ERR_PARENT_DIR_MISSING); else conn_resp_zero(CR_OK_PATH_NOT_FOUND); } else cmd_error = strerror(errno); break; } else if ( csync_check_pure(tag[2]) ) { conn_resp_zero(CR_OK_NOT_FOUND); break; } conn_resp(CR_OK_DATA_FOLLOWS); conn_printf("%s\n", csync_genchecktxt(&st, tag[2], 1)); if ( S_ISREG(st.st_mode) ) csync_rs_sig(tag[2]); else conn_printf("octet-stream 0\n"); } break; case A_MARK: csync_mark(tag[2], peer, 0); break; case A_TYPE: { FILE *f = fopen(prefixsubst(tag[2]), "rb"); if (!f && errno == ENOENT) f = fopen("/dev/null", "rb"); if (f) { char buffer[512]; size_t rc; conn_resp(CR_OK_DATA_FOLLOWS); while ( (rc=fread(buffer, 1, 512, f)) > 0 ) if ( conn_write(buffer, rc) != rc ) { conn_printf("[[ %s ]]", strerror(errno)); break; } fclose(f); return; } cmd_error = strerror(errno); } break; case A_GETTM: case A_GETSZ: { struct stat sbuf; conn_resp(CR_OK_DATA_FOLLOWS); if (!lstat_strict(prefixsubst(tag[2]), &sbuf)) conn_printf("%ld\n", cmdtab[cmdnr].action == A_GETTM ? (long)sbuf.st_mtime : (long)sbuf.st_size); else conn_printf("-1\n"); goto next_cmd; } break; case A_FLUSH: SQL("Flushing dirty entry (if any) for file", "DELETE FROM dirty WHERE filename = '%s'", url_encode(tag[2])); break; case A_DEL: if (!csync_file_backup(tag[2])) csync_unlink(tag[2], 0); break; case A_PATCH: if (!csync_file_backup(tag[2])) { conn_resp(CR_OK_SEND_DATA); csync_rs_sig(tag[2]); if (csync_rs_patch(tag[2])) cmd_error = strerror(errno); } break; case A_MKDIR: /* ignore errors on creating directories if the * directory does exist already. we don't need such * a check for the other file types, because they * will simply be unlinked if already present. */ #ifdef __CYGWIN__ // This creates the file using the native windows API, bypassing // the cygwin wrappers and so making sure that we do not mess up the // permissions.. { char winfilename[MAX_PATH]; cygwin_conv_to_win32_path(prefixsubst(tag[2]), winfilename); if ( !CreateDirectory(TEXT(winfilename), NULL) ) { struct stat st; if ( lstat_strict(prefixsubst(tag[2]), &st) != 0 || !S_ISDIR(st.st_mode)) { csync_debug(1, "Win32 I/O Error %d in mkdir command: %s\n", (int)GetLastError(), winfilename); cmd_error = conn_response(CR_ERR_WIN32_EIO_CREATE_DIR); } } } #else if ( mkdir(prefixsubst(tag[2]), 0700) ) { struct stat st; if ( lstat_strict((prefixsubst(tag[2])), &st) != 0 || !S_ISDIR(st.st_mode)) cmd_error = strerror(errno); } #endif break; case A_MKCHR: if ( mknod(prefixsubst(tag[2]), 0700|S_IFCHR, atoi(tag[3])) ) cmd_error = strerror(errno); break; case A_MKBLK: if ( mknod(prefixsubst(tag[2]), 0700|S_IFBLK, atoi(tag[3])) ) cmd_error = strerror(errno); break; case A_MKFIFO: if ( mknod(prefixsubst(tag[2]), 0700|S_IFIFO, 0) ) cmd_error = strerror(errno); break; case A_MKLINK: if ( symlink(tag[3], prefixsubst(tag[2])) ) cmd_error = strerror(errno); break; case A_MKSOCK: /* just ignore socket files */ break; case A_SETOWN: if ( !csync_ignore_uid || !csync_ignore_gid ) { int uid = csync_ignore_uid ? -1 : atoi(tag[3]); int gid = csync_ignore_gid ? -1 : atoi(tag[4]); if ( lchown(prefixsubst(tag[2]), uid, gid) ) cmd_error = strerror(errno); } break; case A_SETMOD: if ( !csync_ignore_mod ) { if ( chmod(prefixsubst(tag[2]), atoi(tag[3])) ) cmd_error = strerror(errno); } break; case A_SETIME: { struct utimbuf utb; utb.actime = atoll(tag[3]); utb.modtime = atoll(tag[3]); if ( utime(prefixsubst(tag[2]), &utb) ) cmd_error = strerror(errno); } break; case A_LIST: SQL_BEGIN("DB Dump - Files for sync pair", "SELECT checktxt, filename FROM file %s%s%s ORDER BY filename", strcmp(tag[2], "-") ? "WHERE filename = '" : "", strcmp(tag[2], "-") ? url_encode(tag[2]) : "", strcmp(tag[2], "-") ? "'" : "") { if ( csync_match_file_host(url_decode(SQL_V(1)), tag[1], peer, (const char **)&tag[3]) ) conn_printf("%s\t%s\n", SQL_V(0), SQL_V(1)); } SQL_END; break; case A_DEBUG: csync_debug_out = stdout; if ( tag[1][0] ) csync_debug_level = atoi(tag[1]); break; case A_HELLO: if (peer) { free(peer); peer = NULL; } if (verify_peername(tag[1], &peername)) { peer = strdup(tag[1]); } else { peer = NULL; cmd_error = conn_response(CR_ERR_IDENTIFICATION_FAILED); break; } #ifdef HAVE_LIBGNUTLS if (!csync_conn_usessl) { struct csync_nossl *t; for (t = csync_nossl; t; t=t->next) { if ( !fnmatch(t->pattern_from, myhostname, 0) && !fnmatch(t->pattern_to, peer, 0) ) goto conn_without_ssl_ok; } cmd_error = conn_response(CR_ERR_SSL_EXPECTED); } conn_without_ssl_ok:; #endif break; case A_GROUP: if (active_grouplist) { cmd_error = conn_response(CR_ERR_GROUP_LIST_ALREADY_SET); } else { const struct csync_group *g; int i, gnamelen; active_grouplist = strdup(tag[1]); for (g=csync_group; g; g=g->next) { if (!g->myname) continue; i=0; gnamelen = strlen(csync_group->gname); while (active_grouplist[i]) { if ( !strncmp(active_grouplist+i, csync_group->gname, gnamelen) && (active_grouplist[i+gnamelen] == ',' || !active_grouplist[i+gnamelen]) ) goto found_asactive; while (active_grouplist[i]) if (active_grouplist[i++]==',') break; } csync_group->myname = 0; found_asactive: ; } } break; case A_BYE: for (i=0; i<32; i++) free(tag[i]); conn_resp(CR_OK_CU_LATER); return; } if ( cmdtab[cmdnr].update ) csync_file_update(tag[2], peer); if ( cmdtab[cmdnr].update == 1 ) { csync_debug(1, "Updated %s from %s.\n", tag[2], peer ? peer : "???"); csync_schedule_commands(tag[2], 0); } abort_cmd: if ( cmd_error ) conn_printf("%s\n", cmd_error); else conn_resp(CR_OK_CMD_FINISHED); next_cmd: destroy_tag(tag); } } csync2-2.0-22-gce67c55/db.c000066400000000000000000000170051333746731400147720ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include "db_api.h" #define DEADLOCK_MESSAGE \ "Database backend is exceedingly busy => Terminating (requesting retry).\n" int db_blocking_mode = 1; int db_sync_mode = 1; extern int db_type; static db_conn_p db = 0; // TODO make configurable int wait = 1; static int get_dblock_timeout() { return getpid() % 7 + csync_lock_timeout; } static int tqueries_counter = -50; static time_t transaction_begin = 0; static time_t last_wait_cycle = 0; static int begin_commit_recursion = 0; static int in_sql_query = 0; void csync_db_alarmhandler(int signum) { if ( in_sql_query || begin_commit_recursion ) alarm(2); if (tqueries_counter <= 0) return; begin_commit_recursion++; csync_debug(2, "Database idle in transaction. Forcing COMMIT.\n"); SQL("COMMIT ", "COMMIT "); tqueries_counter = -10; begin_commit_recursion--; } void csync_db_maybegin() { if ( !db_blocking_mode || begin_commit_recursion ) return; begin_commit_recursion++; signal(SIGALRM, SIG_IGN); alarm(0); tqueries_counter++; if (tqueries_counter <= 0) { begin_commit_recursion--; return; } if (tqueries_counter == 1) { transaction_begin = time(0); if (!last_wait_cycle) last_wait_cycle = transaction_begin; SQL("BEGIN ", "BEGIN "); } begin_commit_recursion--; } void csync_db_maycommit() { time_t now; if ( !db_blocking_mode || begin_commit_recursion ) return; begin_commit_recursion++; if (tqueries_counter <= 0) { begin_commit_recursion--; return; } now = time(0); if ((now - last_wait_cycle) > 10) { SQL("COMMIT", "COMMIT "); if (wait) { csync_debug(2, "Waiting %d secs so others can lock the database (%d - %d)...\n", wait, (int)now, (int)last_wait_cycle); sleep(wait); } last_wait_cycle = 0; tqueries_counter = -10; begin_commit_recursion--; return; } if ((tqueries_counter > 1000) || ((now - transaction_begin) > 3)) { SQL("COMMIT ", "COMMIT "); tqueries_counter = 0; begin_commit_recursion--; return; } signal(SIGALRM, csync_db_alarmhandler); alarm(10); begin_commit_recursion--; return; } void csync_db_open(const char *file) { int rc = db_open(file, db_type, &db); if ( rc != DB_OK ) csync_fatal("Can't open database: %s\n", file); db_set_logger(db, csync_debug); /* ignore errors on table creation */ in_sql_query++; if (db_schema_version(db) < DB_SCHEMA_VERSION) if (db_upgrade_to_schema(db, DB_SCHEMA_VERSION) != DB_OK) csync_fatal("Cannot create database tables (version requested = %d): %s\n", DB_SCHEMA_VERSION, db_errmsg(db)); if (!db_sync_mode) db_exec(db, "PRAGMA synchronous = OFF"); in_sql_query--; // return db; } void csync_db_close() { if (!db || begin_commit_recursion) return; begin_commit_recursion++; if (tqueries_counter > 0) { SQL("COMMIT ", "COMMIT "); tqueries_counter = -10; } db_close(db); begin_commit_recursion--; db = 0; } void csync_db_sql(const char *err, const char *fmt, ...) { char *sql; va_list ap; int rc, busyc = 0; va_start(ap, fmt); VASPRINTF(&sql, fmt, ap); va_end(ap); in_sql_query++; csync_db_maybegin(); csync_debug(2, "SQL: %s\n", sql); while (1) { rc = db_exec(db, sql); if ( rc != DB_BUSY ) break; if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } csync_debug(2, "Database is busy, sleeping a sec.\n"); sleep(1); } if ( rc != DB_OK && err ) csync_fatal("Database Error: %s [%d]: %s on executing %s\n", err, rc, db_errmsg(db), sql); free(sql); csync_db_maycommit(); in_sql_query--; } void* csync_db_begin(const char *err, const char *fmt, ...) { db_stmt_p stmt = NULL; char *sql; va_list ap; int rc, busyc = 0; char *ppTail; va_start(ap, fmt); VASPRINTF(&sql, fmt, ap); va_end(ap); in_sql_query++; csync_db_maybegin(); csync_debug(2, "SQL: %s\n", sql); while (1) { rc = db_prepare_stmt(db, sql, &stmt, &ppTail); if ( rc != DB_BUSY ) break; if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } csync_debug(2, "Database is busy, sleeping a sec.\n"); sleep(1); } if ( rc != DB_OK && err ) csync_fatal("Database Error: %s [%d]: %s on executing %s\n", err, rc, db_errmsg(db), sql); free(sql); return stmt; } const char *csync_db_get_column_text(void *stmt, int column) { return db_stmt_get_column_text(stmt, column); } int csync_db_get_column_int(void *stmt, int column) { return db_stmt_get_column_int((db_stmt_p) stmt, column); } int csync_db_next(void *vmx, const char *err, int *pN, const char ***pazValue, const char ***pazColName) { db_stmt_p stmt = vmx; int rc, busyc = 0; csync_debug(4, "Trying to fetch a row from the database.\n"); while (1) { rc = db_stmt_next(stmt); if ( rc != DB_BUSY ) break; if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } csync_debug(2, "Database is busy, sleeping a sec.\n"); sleep(1); } if ( rc != DB_OK && rc != DB_ROW && rc != DB_DONE && err ) csync_fatal("Database Error: %s [%d]: %s\n", err, rc, db_errmsg(db)); return rc == DB_ROW; } const void * csync_db_colblob(void *stmtx, int col) { db_stmt_p stmt = stmtx; const void *ptr = stmt->get_column_blob(stmt, col); if (stmt->db && stmt->db->logger) { stmt->db->logger(4, "DB get blob: %s ", (char *) ptr); } return ptr; } void csync_db_fin(void *vmx, const char *err) { db_stmt_p stmt = (db_stmt_p) vmx; int rc, busyc = 0; if (vmx == NULL) return; csync_debug(2, "SQL Query finished.\n"); while (1) { rc = db_stmt_close(stmt); if ( rc != DB_BUSY ) break; if (busyc++ > get_dblock_timeout()) { db = 0; csync_fatal(DEADLOCK_MESSAGE); } csync_debug(2, "Database is busy, sleeping a sec.\n"); sleep(1); } if ( rc != DB_OK && err ) csync_fatal("Database Error: %s [%d]: %s\n", err, rc, db_errmsg(db)); csync_db_maycommit(); in_sql_query--; } char *db_default_database(char *dbdir) { char *db; if (!dbdir || !dbdir[0]) dbdir = DBDIR; #if defined(HAVE_SQLITE3) ASPRINTF(&db, "sqlite3://%s/%s%s%s.db3", dbdir, myhostname, cfgname[0] ? "_" : "", cfgname); #elif defined(HAVE_SQLITE) ASPRINTF(&db, "sqlite2://%s/%s%s%s.db", dbdir, myhostname, cfgname[0] ? "_" : "", cfgname); #elif defined(HAVE_MYSQL) ASPRINTF(&db, "mysql://root@localhost/csync2_%s%s%s", myhostname, cfgname[0] ? "_" : "", cfgname); #elif defined(HAVE_POSTGRES) ASPRINTF(&db, "pgsql://root@localhost/csync2_%s%s%s", myhostname, cfgname[0] ? "_" : "", cfgname); #else #error "No database backend available. Please install either libpg, libmysqlclient or libsqlite, reconfigure and recompile" #endif return db; } csync2-2.0-22-gce67c55/db_api.c000066400000000000000000000144271333746731400156300ustar00rootroot00000000000000/* DB API */ #include "csync2.h" #include #include #include #include #include #include #include "db_api.h" #include "db_mysql.h" #include "db_postgres.h" #include "db_sqlite.h" #include "db_sqlite2.h" #define DEADLOCK_MESSAGE \ "Database backend is exceedingly busy => Terminating (requesting retry).\n" int db_detect_type(const char **db_str, int type) { /* *INDENT-OFF* */ const char *db_types[] = { "mysql://", "sqlite://", "sqlite3://", "sqlite2://", "pgsql://", 0 }; const int types[] = { DB_MYSQL, DB_SQLITE3, DB_SQLITE3, DB_SQLITE2, DB_PGSQL, DB_UNKNOWN_SCHEME }; /* *INDENT-ON* */ int index; if (*db_str[0] == '/') return DB_SQLITE3; for (index = 0; 1; index++) { if (db_types[index] == 0) break; if (!strncmp(*db_str, db_types[index], strlen(db_types[index]))) { *db_str += strlen(db_types[index]); return types[index]; } } if (strstr(*db_str, "://")) return DB_UNKNOWN_SCHEME; return type; } /* expects the "scheme://" prefix to be removed already! * eg. if full url had been * mysql://user:password@host:port/db * input url to this function: * user:password@host:port/db * Note: * if no database is specified, it will default to * csync2__ * or csync2_, if no explicit config name is used. * * Note that the output arguments *host, *user, *pass and *database will be initialized. * *port should be initialized to the default port before calling this function. * * TODO: * add support for unix domain sockets? **/ void csync_parse_url(char *url, char **host, char **user, char **pass, char **database, unsigned int *port) { char *pos = strchr(url, '@'); if (pos) { /* Optional user/passwd */ *pos = '\0'; *user = url; url = pos + 1; /* password */ pos = strchr(*user, ':'); if (pos) { *pos = '\0'; *pass = pos + 1; } else *pass = NULL; } else { /* No user:pass@ */ *user = NULL; *pass = NULL; } *host = url; pos = strchr(*host, '/'); if (pos) { // Database *pos = '\0'; *database = pos + 1; } else *database = NULL; pos = strchr(*host, ':'); if (pos) { *pos = '\0'; *port = atoi(pos + 1); } if (!*database || !*database[0]) ASPRINTF(database, "csync2_%s%s%s", myhostname, cfgname[0] ? "_" : "", cfgname); /* I just don't want to know about special characters, * or differences between db engines. */ for (pos = *database; *pos; pos++) { switch (*pos) { case 'a' ... 'z': case 'A' ... 'Z': case '0' ... '9': case '_': break; default: *pos = '_'; } } } int db_open(const char *db_str, int type, db_conn_p * db) { int rc = DB_ERROR; type = db_detect_type(&db_str, type); if (type == DB_SQLITE2 || type == DB_SQLITE3) { struct stat sbuf; if (stat(db_str, &sbuf) == 0 && S_ISDIR(sbuf.st_mode)) { /* trim trailing slashes; don't trim "/". */ size_t len = strlen(db_str); char *tmp = strdup(db_str); while (len > 1 && tmp[--len] == '/') tmp[len] = '\0'; ASPRINTF((char**)&db_str, "%s/%s%s%s.db%s", tmp, myhostname, cfgname[0] ? "_" : "", cfgname, (type == DB_SQLITE3) ? "3" : ""); free(tmp); } } /* Switch between implementation */ switch (type) { case DB_UNKNOWN_SCHEME: csync_fatal("Unknown database scheme: %s\n", db_str); break; case DB_SQLITE2: rc = db_sqlite2_open(db_str, db); if (rc != DB_OK && db_str[0] != '/') fprintf(csync_debug_out, "Cannot open database file: %s, maybe you need three slashes (like sqlite:///var/lib/csync2/csync2.db)\n", db_str); break; case DB_SQLITE3: rc = db_sqlite_open(db_str, db); if (rc != DB_OK && db_str[0] != '/') fprintf(csync_debug_out, "Cannot open database file: %s, maybe you need three slashes (like sqlite:///var/lib/csync2/csync2.db)\n", db_str); break; case DB_MYSQL: #ifdef HAVE_MYSQL rc = db_mysql_open(db_str, db); #else csync_fatal("No Mysql support configured. Please reconfigure with --enable-mysql (database is %s).\n", db_str); #endif break; case DB_PGSQL: #ifdef HAVE_POSTGRES rc = db_postgres_open(db_str, db); #else csync_fatal("No Postgres SQL support configured. Please reconfigure with --enable-postgres (database is %s).\n", db_str); #endif break; default: csync_fatal("Database type not found. Can't open database %s\n", db_str); rc = DB_ERROR; } if (*db) (*db)->logger = 0; return rc; } void db_set_logger(db_conn_p conn, void (*logger) (int lv, const char *fmt, ...)) { if (conn == NULL) csync_fatal("No connection in set_logger.\n"); conn->logger = logger; } void db_close(db_conn_p conn) { if (!conn || !conn->close) return; conn->close(conn); } const char *db_errmsg(db_conn_p conn) { if (conn && conn->errmsg) return conn->errmsg(conn); return "(no error message function available)"; } int db_exec(db_conn_p conn, const char *sql) { if (conn && conn->exec) return conn->exec(conn, sql); csync_debug(0, "No exec function in db_exec.\n"); return DB_ERROR; } int db_prepare_stmt(db_conn_p conn, const char *sql, db_stmt_p * stmt, char **pptail) { if (conn && conn->prepare) return conn->prepare(conn, sql, stmt, pptail); csync_debug(0, "No prepare function in db_prepare_stmt.\n"); return DB_ERROR; } const char *db_stmt_get_column_text(db_stmt_p stmt, int column) { if (stmt && stmt->get_column_text) return stmt->get_column_text(stmt, column); csync_debug(0, "No stmt in db_stmt_get_column_text / no function.\n"); return NULL; } int db_stmt_get_column_int(db_stmt_p stmt, int column) { if (stmt && stmt->get_column_int) return stmt->get_column_int(stmt, column); csync_debug(0, "No stmt in db_stmt_get_column_int / no function.\n"); return 0; } int db_stmt_next(db_stmt_p stmt) { if (stmt && stmt->next) return stmt->next(stmt); csync_debug(0, "No stmt in db_stmt_next / no function.\n"); return DB_ERROR; } int db_stmt_close(db_stmt_p stmt) { if (stmt && stmt->close) return stmt->close(stmt); csync_debug(0, "No stmt in db_stmt_close / no function.\n"); return DB_ERROR; } int db_schema_version(db_conn_p db) { int version = -1; SQL_BEGIN(NULL, /* ignore errors */ "SELECT count(*) from file") { version = 0; } SQL_END; return version; } int db_upgrade_to_schema(db_conn_p db, int version) { if (db && db->upgrade_to_schema) return db->upgrade_to_schema(version); return DB_ERROR; } csync2-2.0-22-gce67c55/db_api.h000066400000000000000000000046011333746731400156260ustar00rootroot00000000000000 #ifndef DB_API_H #define DB_API_H #define DB_UNKNOWN_SCHEME -1 #define DB_SQLITE2 1 #define DB_SQLITE3 2 #define DB_MYSQL 3 #define DB_PGSQL 4 #define DB_OK 0 #define DB_ERROR 1 #define DB_BUSY 2 #define DB_NO_CONNECTION 3 #define DB_NO_CONNECTION_REAL 4 #define DB_ROW 100 #define DB_DONE 101 typedef struct db_conn_t *db_conn_p; typedef struct db_stmt_t *db_stmt_p; struct db_conn_t { void *private; int (*exec) (db_conn_p conn, const char* exec); int (*prepare)(db_conn_p conn, const char *statement, db_stmt_p *stmt, char **value); void (*close) (db_conn_p conn); void (*logger) (int lv, const char *fmt, ...); const char* (*errmsg) (db_conn_p conn); int (*upgrade_to_schema) (int version); }; struct db_stmt_t { void *private; void *private2; db_conn_p db; const char * (*get_column_text) (db_stmt_p vmx, int column); const void* (*get_column_blob) (db_stmt_p vmx, int column); int (*get_column_int) (db_stmt_p vmx, int column); int (*next) (db_stmt_p stmt); int (*close)(db_stmt_p stmt); }; //struct db_conn *db_conn; /* Design BUGs: * Does expect the scheme:// part of the url to be already stripped! * Does change its *url argument. * Note: * *port will be unchanged, if no port is mentioned. * *host, *user, *pass and *database will all be explicitly set, * if nothing appropriate is found in url, they will be initialized to NULL. **/ void csync_parse_url(char *url, char **host, char **user, char **pass, char **database, unsigned int *port); int db_open(const char *file, int type, db_conn_p *db); void db_close(db_conn_p conn); int db_exec(db_conn_p conn, const char* exec); int db_exec2(db_conn_p conn, const char* exec, void (*callback)(void *, int, int), void *data, const char **err); int db_prepare_stmt(db_conn_p conn, const char *statement, db_stmt_p *stmt, char **value); const char * db_stmt_get_column_text(db_stmt_p stmt, int column); int db_stmt_get_column_int(db_stmt_p stmt, int column); int db_stmt_next (db_stmt_p stmt); int db_stmt_close(db_stmt_p stmt); void db_set_logger(db_conn_p conn, void (*logger)(int lv, const char *fmt, ...)); int db_schema_version(db_conn_p db); int db_upgrade_to_schema(db_conn_p db, int version); const char *db_errmsg(db_conn_p conn); #endif csync2-2.0-22-gce67c55/db_mysql.c000066400000000000000000000240311333746731400162140ustar00rootroot00000000000000/* * Copyright (C) 2010 Dennis Schafroth > * Copyright (C) 2010 Johannes Thoma * Copyright (C) 2010 - 2013 LINBIT Information Technologies GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include "db_api.h" #include "db_mysql.h" #include "dl.h" #ifdef HAVE_MYSQL #include #include static struct db_mysql_fns { MYSQL *(*mysql_init_fn) (MYSQL *); MYSQL *(*mysql_real_connect_fn) (MYSQL *, const char *, const char *, const char *, const char *, unsigned int, const char *, unsigned long); int (*mysql_errno_fn) (MYSQL *); int (*mysql_query_fn) (MYSQL *, const char *); void (*mysql_close_fn) (MYSQL *); const char *(*mysql_error_fn) (MYSQL *); MYSQL_RES *(*mysql_store_result_fn) (MYSQL *); unsigned int (*mysql_num_fields_fn) (MYSQL_RES *); MYSQL_ROW(*mysql_fetch_row_fn) (MYSQL_RES *); void (*mysql_free_result_fn) (MYSQL_RES *); unsigned int (*mysql_warning_count_fn) (MYSQL *); } f; static void *dl_handle; static void db_mysql_dlopen(void) { csync_debug(2, "Opening shared library libmysqlclient.so\n"); dl_handle = dlopen("libmysqlclient.so", RTLD_LAZY); if (dl_handle == NULL) { csync_fatal ("Could not open libmysqlclient.so: %s\n" "Please install Mysql client library (libmysqlclient) or use other database (sqlite, postgres)\n", dlerror()); } csync_debug(2, "Reading symbols from shared library libmysqlclient.so\n"); LOOKUP_SYMBOL(dl_handle, mysql_init); LOOKUP_SYMBOL(dl_handle, mysql_real_connect); LOOKUP_SYMBOL(dl_handle, mysql_errno); LOOKUP_SYMBOL(dl_handle, mysql_query); LOOKUP_SYMBOL(dl_handle, mysql_close); LOOKUP_SYMBOL(dl_handle, mysql_error); LOOKUP_SYMBOL(dl_handle, mysql_store_result); LOOKUP_SYMBOL(dl_handle, mysql_num_fields); LOOKUP_SYMBOL(dl_handle, mysql_fetch_row); LOOKUP_SYMBOL(dl_handle, mysql_free_result); LOOKUP_SYMBOL(dl_handle, mysql_warning_count); } int db_mysql_open(const char *file, db_conn_p * conn_p) { db_mysql_dlopen(); MYSQL *db = f.mysql_init_fn(0); char *host, *user, *pass, *database; unsigned int port = 0; char *db_url = malloc(strlen(file) + 1); char *create_database_statement; if (db_url == NULL) csync_fatal("No memory for db_url\n"); strcpy(db_url, file); csync_parse_url(db_url, &host, &user, &pass, &database, &port); if (f.mysql_real_connect_fn(db, host, user, pass, database, port, NULL, 0) == NULL) { if (f.mysql_errno_fn(db) != ER_BAD_DB_ERROR) goto fatal; if (f.mysql_real_connect_fn(db, host, user, pass, NULL, port, NULL, 0) == NULL) goto fatal; ASPRINTF(&create_database_statement, "create database %s", database); csync_debug(2, "creating database %s\n", database); if (f.mysql_query_fn(db, create_database_statement) != 0) csync_fatal("Cannot create database %s: Error: %s\n", database, f.mysql_error_fn(db)); free(create_database_statement); f.mysql_close_fn(db); db = f.mysql_init_fn(0); if (f.mysql_real_connect_fn(db, host, user, pass, database, port, NULL, 0) == NULL) goto fatal; } db_conn_p conn = calloc(1, sizeof(*conn)); if (conn == NULL) return DB_ERROR; *conn_p = conn; conn->private = db; conn->close = db_mysql_close; conn->exec = db_mysql_exec; conn->prepare = db_mysql_prepare; conn->errmsg = db_mysql_errmsg; conn->upgrade_to_schema = db_mysql_upgrade_to_schema; return DB_OK; fatal: csync_fatal("Failed to connect to database: Error: %s\n", f.mysql_error_fn(db)); return DB_ERROR; } void db_mysql_close(db_conn_p conn) { if (!conn) return; if (!conn->private) return; f.mysql_close_fn(conn->private); conn->private = 0; } const char *db_mysql_errmsg(db_conn_p conn) { if (!conn) return "(no connection)"; if (!conn->private) return "(no private data in conn)"; return f.mysql_error_fn(conn->private); } static void print_warnings(int level, MYSQL * m) { int rc; MYSQL_RES *res; int fields; MYSQL_ROW row; if (m == NULL) csync_fatal("print_warnings: m is NULL"); rc = f.mysql_query_fn(m, "SHOW WARNINGS"); if (rc != 0) csync_fatal("print_warnings: Failed to get warning messages"); res = f.mysql_store_result_fn(m); if (res == NULL) csync_fatal("print_warnings: Failed to get result set for warning messages"); fields = f.mysql_num_fields_fn(res); if (fields < 2) csync_fatal("print_warnings: Strange: show warnings result set has less than 2 rows"); row = f.mysql_fetch_row_fn(res); while (row) { csync_debug(level, "MySql Warning: %s\n", row[2]); row = f.mysql_fetch_row_fn(res); } f.mysql_free_result_fn(res); } int db_mysql_exec(db_conn_p conn, const char *sql) { int rc = DB_ERROR; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } rc = f.mysql_query_fn(conn->private, sql); /* Treat warnings as errors. * For example when a column is too short this should be an error. */ if (f.mysql_warning_count_fn(conn->private) > 0) { print_warnings(1, conn->private); return DB_ERROR; } /* On error parse, create DB ERROR element */ return rc; } int db_mysql_prepare(db_conn_p conn, const char *sql, db_stmt_p * stmt_p, char **pptail) { int rc = DB_ERROR; *stmt_p = NULL; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } db_stmt_p stmt = malloc(sizeof(*stmt)); /* TODO avoid strlen, use configurable limit? */ rc = f.mysql_query_fn(conn->private, sql); if (f.mysql_warning_count_fn(conn->private) > 0) { print_warnings(1, conn->private); return DB_ERROR; } MYSQL_RES *mysql_stmt = f.mysql_store_result_fn(conn->private); if (mysql_stmt == NULL) { csync_debug(2, "Error in mysql_store_result: %s\n", f.mysql_error_fn(conn->private)); return DB_ERROR; } if (f.mysql_warning_count_fn(conn->private) > 0) { print_warnings(1, conn->private); return DB_ERROR; } stmt->private = mysql_stmt; /* TODO error mapping / handling */ *stmt_p = stmt; stmt->get_column_text = db_mysql_stmt_get_column_text; stmt->get_column_blob = db_mysql_stmt_get_column_blob; stmt->get_column_int = db_mysql_stmt_get_column_int; stmt->next = db_mysql_stmt_next; stmt->close = db_mysql_stmt_close; stmt->db = conn; return DB_OK; } const void *db_mysql_stmt_get_column_blob(db_stmt_p stmt, int column) { if (!stmt || !stmt->private2) { return 0; } MYSQL_ROW row = stmt->private2; return row[column]; } const char *db_mysql_stmt_get_column_text(db_stmt_p stmt, int column) { if (!stmt || !stmt->private2) { return 0; } MYSQL_ROW row = stmt->private2; return row[column]; } int db_mysql_stmt_get_column_int(db_stmt_p stmt, int column) { const char *value = db_mysql_stmt_get_column_text(stmt, column); if (value) return atoi(value); /* error mapping */ return 0; } int db_mysql_stmt_next(db_stmt_p stmt) { MYSQL_RES *mysql_stmt = stmt->private; stmt->private2 = f.mysql_fetch_row_fn(mysql_stmt); /* error mapping */ if (stmt->private2) return DB_ROW; return DB_DONE; } int db_mysql_stmt_close(db_stmt_p stmt) { MYSQL_RES *mysql_stmt = stmt->private; f.mysql_free_result_fn(mysql_stmt); free(stmt); return DB_OK; } /* NOTE: * NI_MAXHOST is 1025. That should be plenty for typical hostnames. * * Use TEXT fields: * PATH_MAX is typically 4096, and that is assumed elsewhere in the code as * well. But as all filenames (and other interesting fields) are stored as * transformed by url_encode(), they can be three times as long. * checktxt may be very long, if it stores the target of a very long symlink. * * The INTEGER are actually used as boolean flags. * * prefix limit MyISAM: 1000 bytes, InnoDB: 767 bytes. * Which is also the max key length. * utf8: up to 3 bytes per "character" --> sum of key prefix lengths: 767/3 = 255 * * We must not define UNIQUE keys on prefixes, * or we would not be able to handle long path names. * We should be able to get away with just "key", * typically the code does "delete from" before "insert into" anyways. * */ int db_mysql_upgrade_to_schema(int version) { if (version < 0) return DB_OK; if (version > 0) return DB_ERROR; csync_debug(2, "Upgrading database schema to version %d.\n", version); /* *INDENT-OFF* */ csync_db_sql("Creating action table", "CREATE TABLE action (" " filename TEXT NOT NULL," " command TEXT NOT NULL," " logfile TEXT NOT NULL," " KEY filename (filename(255))," " KEY command (command(255))" ");"); csync_db_sql("Creating dirty table", "CREATE TABLE dirty (" " filename TEXT NOT NULL," " forced INTEGER NOT NULL," " myname TEXT NOT NULL," " peername TEXT NOT NULL," " KEY filename (filename(255))," " KEY dirty_host (peername(255))" ");"); csync_db_sql("Creating file table", "CREATE TABLE file (" " filename TEXT NOT NULL," " checktxt TEXT NOT NULL," " KEY filename (filename(255))" ");"); csync_db_sql("Creating hint table", "CREATE TABLE hint (" " filename TEXT NOT NULL," " recursive INTEGER NOT NULL" ");"); csync_db_sql("Creating x509_cert table", "CREATE TABLE x509_cert (" " peername TEXT NOT NULL," " certdata TEXT NOT NULL," " KEY peername (peername(255))" ");"); /* *INDENT-ON* */ return DB_OK; } #endif csync2-2.0-22-gce67c55/db_mysql.h000066400000000000000000000013011333746731400162140ustar00rootroot00000000000000 #ifndef DB_MYSQL_H #define DB_MYSQL_H /* public */ int db_mysql_open(const char *file, db_conn_p *conn_p); /* Private */ void db_mysql_close(db_conn_p db_conn); int db_mysql_exec(db_conn_p conn, const char *sql); int db_mysql_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); int db_mysql_stmt_next(db_stmt_p stmt); const void* db_mysql_stmt_get_column_blob(db_stmt_p stmt, int column); const char *db_mysql_stmt_get_column_text(db_stmt_p stmt, int column); int db_mysql_stmt_get_column_int(db_stmt_p stmt, int column); int db_mysql_stmt_close(db_stmt_p stmt); const char *db_mysql_errmsg(db_conn_p db_conn); int db_mysql_upgrade_to_schema(int version); #endif csync2-2.0-22-gce67c55/db_postgres.c000066400000000000000000000245451333746731400167270ustar00rootroot00000000000000/* * Copyright (C) 2010 Dennis Schafroth * Copyright (C) 2010 Johannes Thoma * Copyright (C) 2010 - 2013 LINBIT Information Technologies GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include "db_api.h" #include "db_postgres.h" #include "dl.h" #ifdef HAVE_POSTGRES #include #endif #if (!defined HAVE_POSTGRES) int db_postgres_open(const char *file, db_conn_p * conn_p) { return DB_ERROR; } #else static struct db_postgres_fns { PGconn *(*PQconnectdb_fn) (char *); ConnStatusType(*PQstatus_fn) (const PGconn *); char *(*PQerrorMessage_fn) (const PGconn *); void (*PQfinish_fn) (PGconn *); PGresult *(*PQexec_fn) (PGconn *, const char *); ExecStatusType(*PQresultStatus_fn) (const PGresult *); char *(*PQresultErrorMessage_fn) (const PGresult *); void (*PQclear_fn) (PGresult *); int (*PQntuples_fn) (const PGresult *); char *(*PQgetvalue_fn) (const PGresult *, int, int); } f; static void *dl_handle; static void db_postgres_dlopen(void) { csync_debug(2, "Opening shared library libpq.so\n"); dl_handle = dlopen("libpq.so", RTLD_LAZY); if (dl_handle == NULL) { csync_fatal ("Could not open libpq.so: %s\n" "Please install postgres client library (libpg) or use other database (sqlite, mysql)\n", dlerror()); } csync_debug(2, "Reading symbols from shared library libpq.so\n"); LOOKUP_SYMBOL(dl_handle, PQconnectdb); LOOKUP_SYMBOL(dl_handle, PQstatus); LOOKUP_SYMBOL(dl_handle, PQerrorMessage); LOOKUP_SYMBOL(dl_handle, PQfinish); LOOKUP_SYMBOL(dl_handle, PQexec); LOOKUP_SYMBOL(dl_handle, PQresultStatus); LOOKUP_SYMBOL(dl_handle, PQresultErrorMessage); LOOKUP_SYMBOL(dl_handle, PQclear); LOOKUP_SYMBOL(dl_handle, PQntuples); LOOKUP_SYMBOL(dl_handle, PQgetvalue); } /* Thi function parses a URL string like pgsql://[user[:passwd]@]hostname[:port]/database. and returns the result in the given parameters. If an optional keyword is not given, the value of the parameter is not changed. */ int db_postgres_open(const char *file, db_conn_p * conn_p) { PGconn *pg_conn; char *host, *user, *pass, *database; unsigned int port = 5432; /* default postgres port */ char *db_url = strdup(file); char *pg_conn_info; db_postgres_dlopen(); if (db_url == NULL) csync_fatal("No memory for db_url\n"); csync_parse_url(db_url, &host, &user, &pass, &database, &port); ASPRINTF(&pg_conn_info, "host='%s' user='%s' password='%s' dbname='%s' port=%d", host ?: "localhost", user ?: "csync2", pass ?: "", database, port); pg_conn = f.PQconnectdb_fn(pg_conn_info); if (pg_conn == NULL) csync_fatal("No memory for postgress connection handle\n"); if (f.PQstatus_fn(pg_conn) != CONNECTION_OK) { f.PQfinish_fn(pg_conn); free(pg_conn_info); ASPRINTF(&pg_conn_info, "host='%s' user='%s' password='%s' dbname='postgres' port=%d", host, user, pass, port); pg_conn = f.PQconnectdb_fn(pg_conn_info); if (pg_conn == NULL) csync_fatal("No memory for postgress connection handle\n"); if (f.PQstatus_fn(pg_conn) != CONNECTION_OK) { csync_debug(0, "Connection failed: %s", f.PQerrorMessage_fn(pg_conn)); f.PQfinish_fn(pg_conn); free(pg_conn_info); return DB_ERROR; } else { char *create_database_statement; PGresult *res; csync_debug(1, "Database %s not found, trying to create it ...", database); ASPRINTF(&create_database_statement, "create database %s", database); res = f.PQexec_fn(pg_conn, create_database_statement); free(create_database_statement); switch (f.PQresultStatus_fn(res)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: break; default: csync_debug(0, "Could not create database %s: %s", database, f.PQerrorMessage_fn(pg_conn)); return DB_ERROR; } f.PQfinish_fn(pg_conn); free(pg_conn_info); ASPRINTF(&pg_conn_info, "host='%s' user='%s' password='%s' dbname='%s' port=%d", host, user, pass, database, port); pg_conn = f.PQconnectdb_fn(pg_conn_info); if (pg_conn == NULL) csync_fatal("No memory for postgress connection handle\n"); if (f.PQstatus_fn(pg_conn) != CONNECTION_OK) { csync_debug(0, "Connection failed: %s", f.PQerrorMessage_fn(pg_conn)); f.PQfinish_fn(pg_conn); free(pg_conn_info); return DB_ERROR; } } } db_conn_p conn = calloc(1, sizeof(*conn)); if (conn == NULL) csync_fatal("No memory for conn\n"); *conn_p = conn; conn->private = pg_conn; conn->close = db_postgres_close; conn->exec = db_postgres_exec; conn->errmsg = db_postgres_errmsg; conn->prepare = db_postgres_prepare; conn->upgrade_to_schema = db_postgres_upgrade_to_schema; free(pg_conn_info); return DB_OK; } void db_postgres_close(db_conn_p conn) { if (!conn) return; if (!conn->private) return; f.PQfinish_fn(conn->private); conn->private = 0; } const char *db_postgres_errmsg(db_conn_p conn) { if (!conn) return "(no connection)"; if (!conn->private) return "(no private data in conn)"; return f.PQerrorMessage_fn(conn->private); } int db_postgres_exec(db_conn_p conn, const char *sql) { PGresult *res; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } res = f.PQexec_fn(conn->private, sql); switch (f.PQresultStatus_fn(res)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: return DB_OK; default: return DB_ERROR; } } int db_postgres_prepare(db_conn_p conn, const char *sql, db_stmt_p * stmt_p, char **pptail) { PGresult *result; int *row_p; *stmt_p = NULL; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } result = f.PQexec_fn(conn->private, sql); if (result == NULL) csync_fatal("No memory for result\n"); switch (f.PQresultStatus_fn(result)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: break; default: csync_debug(1, "Error in PQexec: %s", f.PQresultErrorMessage_fn(result)); f.PQclear_fn(result); return DB_ERROR; } row_p = malloc(sizeof(*row_p)); if (row_p == NULL) csync_fatal("No memory for row\n"); *row_p = -1; db_stmt_p stmt = malloc(sizeof(*stmt)); if (stmt == NULL) csync_fatal("No memory for stmt\n"); stmt->private = result; stmt->private2 = row_p; *stmt_p = stmt; stmt->get_column_text = db_postgres_stmt_get_column_text; stmt->get_column_blob = db_postgres_stmt_get_column_blob; stmt->get_column_int = db_postgres_stmt_get_column_int; stmt->next = db_postgres_stmt_next; stmt->close = db_postgres_stmt_close; stmt->db = conn; return DB_OK; } const void *db_postgres_stmt_get_column_blob(db_stmt_p stmt, int column) { PGresult *result; int *row_p; if (!stmt || !stmt->private || !stmt->private2) { return 0; } result = (PGresult *) stmt->private; row_p = (int *)stmt->private2; if (*row_p >= f.PQntuples_fn(result) || *row_p < 0) { csync_debug(1, "row index out of range (should be between 0 and %d, is %d)\n", *row_p, f.PQntuples_fn(result)); return NULL; } return f.PQgetvalue_fn(result, *row_p, column); } const char *db_postgres_stmt_get_column_text(db_stmt_p stmt, int column) { PGresult *result; int *row_p; if (!stmt || !stmt->private || !stmt->private2) { return 0; } result = (PGresult *) stmt->private; row_p = (int *)stmt->private2; if (*row_p >= f.PQntuples_fn(result) || *row_p < 0) { csync_debug(1, "row index out of range (should be between 0 and %d, is %d)\n", *row_p, f.PQntuples_fn(result)); return NULL; } return f.PQgetvalue_fn(result, *row_p, column); } int db_postgres_stmt_get_column_int(db_stmt_p stmt, int column) { PGresult *result; int *row_p; if (!stmt || !stmt->private || !stmt->private2) { return 0; } result = (PGresult *) stmt->private; row_p = (int *)stmt->private2; if (*row_p >= f.PQntuples_fn(result) || *row_p < 0) { csync_debug(1, "row index out of range (should be between 0 and %d, is %d)\n", *row_p, f.PQntuples_fn(result)); return 0; } return atoi(f.PQgetvalue_fn(result, *row_p, column)); } int db_postgres_stmt_next(db_stmt_p stmt) { PGresult *result; int *row_p; if (!stmt || !stmt->private || !stmt->private2) { return 0; } result = (PGresult *) stmt->private; row_p = (int *)stmt->private2; (*row_p)++; if (*row_p >= f.PQntuples_fn(result)) return DB_DONE; return DB_ROW; } int db_postgres_stmt_close(db_stmt_p stmt) { PGresult *res = stmt->private; f.PQclear_fn(res); free(stmt->private2); free(stmt); return DB_OK; } int db_postgres_upgrade_to_schema(int version) { if (version < 0) return DB_OK; if (version > 0) return DB_ERROR; csync_debug(2, "Upgrading database schema to version %d.\n", version); /* *INDENT-OFF* */ csync_db_sql("Creating action table", "CREATE TABLE action (" " filename TEXT NOT NULL," " command TEXT NOT NULL," " logfile TEXT NOT NULL," " UNIQUE (filename,command)" ");"); csync_db_sql("Creating dirty table", "CREATE TABLE dirty (" " filename TEXT NOT NULL," " forced INTEGER NOT NULL," " myname TEXT NOT NULL," " peername TEXT NOT NULL," " UNIQUE (filename,peername)" ");"); csync_db_sql("Creating file table", "CREATE TABLE file (" " filename TEXT NOT NULL," " checktxt TEXT NOT NULL," " UNIQUE (filename)" ");"); csync_db_sql("Creating hint table", "CREATE TABLE hint (" " filename TEXT NOT NULL," " recursive INTEGER NOT NULL" ");"); csync_db_sql("Creating x509_cert table", "CREATE TABLE x509_cert (" " peername TEXT NOT NULL," " certdata TEXT NOT NULL," " UNIQUE (peername)" ");"); /* *INDENT-ON* */ return DB_OK; } #endif /* HAVE_POSTGRES */ csync2-2.0-22-gce67c55/db_postgres.h000066400000000000000000000013511333746731400167220ustar00rootroot00000000000000 #ifndef DB_POSTGRES_H #define DB_POSTGRES_H /* public */ int db_postgres_open(const char *file, db_conn_p *conn_p); /* Private */ void db_postgres_close(db_conn_p db_conn); int db_postgres_exec(db_conn_p conn, const char *sql); int db_postgres_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); const char *db_postgres_errmsg(db_conn_p db_conn); int db_postgres_stmt_next(db_stmt_p stmt); const void* db_postgres_stmt_get_column_blob(db_stmt_p stmt, int column); const char *db_postgres_stmt_get_column_text(db_stmt_p stmt, int column); int db_postgres_stmt_get_column_int(db_stmt_p stmt, int column); int db_postgres_stmt_close(db_stmt_p stmt); int db_postgres_upgrade_to_schema(int version); #endif csync2-2.0-22-gce67c55/db_sqlite.c000066400000000000000000000162541333746731400163600ustar00rootroot00000000000000/* * Copyright (C) 2010 Dennis Schafroth * Copyright (C) 2010 Johannes Thoma * Copyright (C) 2010 - 2013 LINBIT Information Technologies GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #if defined(HAVE_SQLITE3) #include #endif #include #include #include #include #include #include #include "db_api.h" #include "db_sqlite.h" #include "dl.h" #ifndef HAVE_SQLITE3 int db_sqlite_open(const char *file, db_conn_p * conn_p) { return DB_ERROR; } #else static struct db_sqlite3_fns { int (*sqlite3_open_fn) (const char *, sqlite3 **); int (*sqlite3_close_fn) (sqlite3 *); const char *(*sqlite3_errmsg_fn) (sqlite3 *); int (*sqlite3_exec_fn) (sqlite3 *, const char *, int (*)(void *, int, char **, char **), void *, char **); int (*sqlite3_prepare_v2_fn) (sqlite3 *, const char *, int, sqlite3_stmt **, const char **pzTail); const unsigned char *(*sqlite3_column_text_fn) (sqlite3_stmt *, int); const void *(*sqlite3_column_blob_fn) (sqlite3_stmt *, int); int (*sqlite3_column_int_fn) (sqlite3_stmt *, int); int (*sqlite3_step_fn) (sqlite3_stmt *); int (*sqlite3_finalize_fn) (sqlite3_stmt *); } f; static void *dl_handle; static void db_sqlite3_dlopen(void) { csync_debug(2, "Opening shared library libsqlite3.so.0\n"); dl_handle = dlopen("libsqlite3.so.0", RTLD_LAZY); if (dl_handle == NULL) { csync_fatal ("Could not open libsqlite3.so.0: %s\n" "Please install sqlite3 client library (libsqlite3) or use other database (postgres, mysql)\n", dlerror()); } csync_debug(2, "Reading symbols from shared library libsqlite3.so.0\n"); LOOKUP_SYMBOL(dl_handle, sqlite3_open); LOOKUP_SYMBOL(dl_handle, sqlite3_close); LOOKUP_SYMBOL(dl_handle, sqlite3_errmsg); LOOKUP_SYMBOL(dl_handle, sqlite3_exec); LOOKUP_SYMBOL(dl_handle, sqlite3_prepare_v2); LOOKUP_SYMBOL(dl_handle, sqlite3_column_text); LOOKUP_SYMBOL(dl_handle, sqlite3_column_blob); LOOKUP_SYMBOL(dl_handle, sqlite3_column_int); LOOKUP_SYMBOL(dl_handle, sqlite3_step); LOOKUP_SYMBOL(dl_handle, sqlite3_finalize); } static int sqlite_errors[] = { SQLITE_OK, SQLITE_ERROR, SQLITE_BUSY, SQLITE_ROW, SQLITE_DONE, -1 }; static int db_errors[] = { DB_OK, DB_ERROR, DB_BUSY, DB_ROW, DB_DONE, -1 }; int db_sqlite_error_map(int sqlite_err) { int index; for (index = 0;; index++) { if (sqlite_errors[index] == -1) return DB_ERROR; if (sqlite_err == sqlite_errors[index]) return db_errors[index]; } } int db_sqlite_open(const char *file, db_conn_p * conn_p) { sqlite3 *db; db_sqlite3_dlopen(); int rc = f.sqlite3_open_fn(file, &db); if (rc != SQLITE_OK) { return db_sqlite_error_map(rc); }; db_conn_p conn = calloc(1, sizeof(*conn)); if (conn == NULL) { return DB_ERROR; } *conn_p = conn; conn->private = db; conn->close = db_sqlite_close; conn->exec = db_sqlite_exec; conn->prepare = db_sqlite_prepare; conn->errmsg = db_sqlite_errmsg; conn->upgrade_to_schema = db_sqlite_upgrade_to_schema; return db_sqlite_error_map(rc); } void db_sqlite_close(db_conn_p conn) { if (!conn) return; if (!conn->private) return; f.sqlite3_close_fn(conn->private); conn->private = 0; } const char *db_sqlite_errmsg(db_conn_p conn) { if (!conn) return "(no connection)"; if (!conn->private) return "(no private data in conn)"; return f.sqlite3_errmsg_fn(conn->private); } int db_sqlite_exec(db_conn_p conn, const char *sql) { int rc; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } rc = f.sqlite3_exec_fn(conn->private, sql, 0, 0, 0); return db_sqlite_error_map(rc); } int db_sqlite_prepare(db_conn_p conn, const char *sql, db_stmt_p * stmt_p, char **pptail) { int rc; *stmt_p = NULL; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } db_stmt_p stmt = malloc(sizeof(*stmt)); sqlite3_stmt *sqlite_stmt = 0; /* TODO avoid strlen, use configurable limit? */ rc = f.sqlite3_prepare_v2_fn(conn->private, sql, strlen(sql), &sqlite_stmt, (const char **)pptail); if (rc != SQLITE_OK) return db_sqlite_error_map(rc); stmt->private = sqlite_stmt; *stmt_p = stmt; stmt->get_column_text = db_sqlite_stmt_get_column_text; stmt->get_column_blob = db_sqlite_stmt_get_column_blob; stmt->get_column_int = db_sqlite_stmt_get_column_int; stmt->next = db_sqlite_stmt_next; stmt->close = db_sqlite_stmt_close; stmt->db = conn; return db_sqlite_error_map(rc); } const char *db_sqlite_stmt_get_column_text(db_stmt_p stmt, int column) { if (!stmt || !stmt->private) { return 0; } sqlite3_stmt *sqlite_stmt = stmt->private; const unsigned char *result = f.sqlite3_column_text_fn(sqlite_stmt, column); /* error handling */ return (const char *)result; } #if defined(HAVE_SQLITE3) const void *db_sqlite_stmt_get_column_blob(db_stmt_p stmtx, int col) { sqlite3_stmt *stmt = stmtx->private; return f.sqlite3_column_blob_fn(stmt, col); } #endif int db_sqlite_stmt_get_column_int(db_stmt_p stmt, int column) { sqlite3_stmt *sqlite_stmt = stmt->private; int rc = f.sqlite3_column_int_fn(sqlite_stmt, column); return db_sqlite_error_map(rc); } int db_sqlite_stmt_next(db_stmt_p stmt) { sqlite3_stmt *sqlite_stmt = stmt->private; int rc = f.sqlite3_step_fn(sqlite_stmt); return db_sqlite_error_map(rc); } int db_sqlite_stmt_close(db_stmt_p stmt) { sqlite3_stmt *sqlite_stmt = stmt->private; int rc = f.sqlite3_finalize_fn(sqlite_stmt); free(stmt); return db_sqlite_error_map(rc); } int db_sqlite_upgrade_to_schema(int version) { if (version < 0) return DB_OK; if (version > 0) return DB_ERROR; csync_debug(2, "Upgrading database schema to version %d.\n", version); /* *INDENT-OFF* */ csync_db_sql("Creating file table", "CREATE TABLE file (" " filename, checktxt," " UNIQUE ( filename ) ON CONFLICT REPLACE" ")"); csync_db_sql("Creating dirty table", "CREATE TABLE dirty (" " filename, forced, myname, peername," " UNIQUE ( filename, peername ) ON CONFLICT IGNORE" ")"); csync_db_sql("Creating hint table", "CREATE TABLE hint (" " filename, recursive," " UNIQUE ( filename, recursive ) ON CONFLICT IGNORE" ")"); csync_db_sql("Creating action table", "CREATE TABLE action (" " filename, command, logfile," " UNIQUE ( filename, command ) ON CONFLICT IGNORE" ")"); csync_db_sql("Creating x509_cert table", "CREATE TABLE x509_cert (" " peername, certdata," " UNIQUE ( peername ) ON CONFLICT IGNORE" ")"); /* *INDENT-ON* */ return DB_OK; } #endif csync2-2.0-22-gce67c55/db_sqlite.h000066400000000000000000000013111333746731400163510ustar00rootroot00000000000000 #ifndef DB_SQLITE_H #define DB_SQLITE_H /* public */ int db_sqlite_open(const char *file, db_conn_p *conn_p); /* Private */ void db_sqlite_close(db_conn_p db_conn); int db_sqlite_exec(db_conn_p conn, const char *sql); int db_sqlite_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); int db_sqlite_stmt_next(db_stmt_p stmt); const char* db_sqlite_stmt_get_column_text(db_stmt_p stmt, int column); const void* db_sqlite_stmt_get_column_blob(db_stmt_p stmt, int column); int db_sqlite_stmt_get_column_int(db_stmt_p stmt, int column); int db_sqlite_stmt_close(db_stmt_p stmt); const char *db_sqlite_errmsg(db_conn_p conn); int db_sqlite_upgrade_to_schema(int version); #endif csync2-2.0-22-gce67c55/db_sqlite2.c000066400000000000000000000150661333746731400164420ustar00rootroot00000000000000/* * Copyright (C) 2010 Dennis Schafroth > * Copyright (C) 2010 Johannes Thoma * Copyright (C) 2010 - 2013 LINBIT Information Technologies GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "db_api.h" #include "config.h" #ifndef HAVE_SQLITE /* dummy function to implement a open that fails */ int db_sqlite2_open(const char *file, db_conn_p * conn_p) { return DB_ERROR; } #else #include #include #include #include #include #include #include #include "db_sqlite2.h" #include static struct db_sqlite_fns { sqlite *(*sqlite_open_fn) (const char *, int, char **); void (*sqlite_close_fn) (sqlite *); int (*sqlite_exec_fn) (sqlite *, char *, int (*)(void *, int, char **, char **), void *, char **); int (*sqlite_compile_fn) (sqlite *, const char *, const char **, sqlite_vm **, char **); int (*sqlite_step_fn) (sqlite_vm *, int *, const char ***, const char ***); int (*sqlite_finalize_fn) (sqlite_vm *, char **); } f; static char *errmsg; static void *dl_handle; static void db_sqlite_dlopen(void) { csync_debug(2, "Opening shared library libsqlite.so\n"); dl_handle = dlopen("libsqlite.so", RTLD_LAZY); if (dl_handle == NULL) { csync_debug(1, "Libsqlite.so not found, trying libsqlite.so.0\n"); dl_handle = dlopen("libsqlite.so.0", RTLD_LAZY); if (dl_handle == NULL) { csync_fatal ("Could not open libsqlite.so: %s\n" "Please install sqlite client library (libsqlite) or use other database (postgres, mysql)\n", dlerror()); } } csync_debug(2, "Opening shared library libsqlite.so\n"); LOOKUP_SYMBOL(dl_handle, sqlite_open); LOOKUP_SYMBOL(dl_handle, sqlite_close); LOOKUP_SYMBOL(dl_handle, sqlite_exec); LOOKUP_SYMBOL(dl_handle, sqlite_compile); LOOKUP_SYMBOL(dl_handle, sqlite_step); LOOKUP_SYMBOL(dl_handle, sqlite_finalize); } int db_sqlite2_open(const char *file, db_conn_p * conn_p) { db_sqlite_dlopen(); sqlite *db = f.sqlite_open_fn(file, 0, &errmsg); if (db == 0) { return DB_ERROR; }; db_conn_p conn = calloc(1, sizeof(*conn)); if (conn == NULL) { return DB_ERROR; } *conn_p = conn; conn->private = db; conn->close = db_sqlite2_close; conn->exec = db_sqlite2_exec; conn->prepare = db_sqlite2_prepare; conn->errmsg = NULL; conn->upgrade_to_schema = db_sqlite2_upgrade_to_schema; return DB_OK; } void db_sqlite2_close(db_conn_p conn) { if (!conn) return; if (!conn->private) return; f.sqlite_close_fn(conn->private); conn->private = 0; } const char *db_sqlite2_errmsg(db_conn_p conn) { if (!conn) return "(no connection)"; if (!conn->private) return "(no private data in conn)"; return errmsg; } int db_sqlite2_exec(db_conn_p conn, const char *sql) { int rc; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } rc = f.sqlite_exec_fn(conn->private, (char *)sql, 0, 0, &errmsg); /* On error parse, create DB ERROR element */ return rc; } int db_sqlite2_prepare(db_conn_p conn, const char *sql, db_stmt_p * stmt_p, char **pptail) { int rc; sqlite *db; *stmt_p = NULL; if (!conn) return DB_NO_CONNECTION; if (!conn->private) { /* added error element */ return DB_NO_CONNECTION_REAL; } db = conn->private; db_stmt_p stmt = malloc(sizeof(*stmt)); sqlite_vm *sqlite_stmt = 0; rc = f.sqlite_compile_fn(db, sql, 0, &sqlite_stmt, &errmsg); if (rc != SQLITE_OK) return 0; stmt->private = sqlite_stmt; *stmt_p = stmt; stmt->get_column_text = db_sqlite2_stmt_get_column_text; stmt->get_column_blob = db_sqlite2_stmt_get_column_blob; stmt->get_column_int = db_sqlite2_stmt_get_column_int; stmt->next = db_sqlite2_stmt_next; stmt->close = db_sqlite2_stmt_close; stmt->db = conn; return DB_OK; } const char *db_sqlite2_stmt_get_column_text(db_stmt_p stmt, int column) { if (!stmt || !stmt->private) { return 0; } sqlite_vm *sqlite_stmt = stmt->private; const char **values = stmt->private2; return values[column]; } const void *db_sqlite2_stmt_get_column_blob(db_stmt_p stmt, int col) { return db_sqlite2_stmt_get_column_text(stmt, col); } int db_sqlite2_stmt_get_column_int(db_stmt_p stmt, int column) { sqlite_vm *sqlite_stmt = stmt->private; const char **values = stmt->private2; const char *str_value = values[column]; int value = 0; if (value) value = atoi(str_value); /* TODO missing way to return error */ return value; } int db_sqlite2_stmt_next(db_stmt_p stmt) { sqlite_vm *sqlite_stmt = stmt->private; const char **dataSQL_V, **dataSQL_N; const char **values; const char **names; int columns; int rc = f.sqlite_step_fn(sqlite_stmt, &columns, &values, &names); stmt->private2 = values; /* TODO error mapping */ return rc; // == SQLITE_ROW; } int db_sqlite2_stmt_close(db_stmt_p stmt) { sqlite_vm *sqlite_stmt = stmt->private; int rc = f.sqlite_finalize_fn(sqlite_stmt, &errmsg); free(stmt); return rc; } int db_sqlite2_upgrade_to_schema(int version) { if (version < 0) return DB_OK; if (version > 0) return DB_ERROR; csync_debug(2, "Upgrading database schema to version %d.\n", version); /* *INDENT-OFF* */ csync_db_sql("Creating file table", "CREATE TABLE file (" " filename, checktxt," " UNIQUE ( filename ) ON CONFLICT REPLACE" ")"); csync_db_sql("Creating dirty table", "CREATE TABLE dirty (" " filename, forced, myname, peername," " UNIQUE ( filename, peername ) ON CONFLICT IGNORE" ")"); csync_db_sql("Creating hint table", "CREATE TABLE hint (" " filename, recursive," " UNIQUE ( filename, recursive ) ON CONFLICT IGNORE" ")"); csync_db_sql("Creating action table", "CREATE TABLE action (" " filename, command, logfile," " UNIQUE ( filename, command ) ON CONFLICT IGNORE" ")"); csync_db_sql("Creating x509_cert table", "CREATE TABLE x509_cert (" " peername, certdata," " UNIQUE ( peername ) ON CONFLICT IGNORE" ")"); /* *INDENT-ON* */ return DB_OK; } #endif csync2-2.0-22-gce67c55/db_sqlite2.h000066400000000000000000000012761333746731400164450ustar00rootroot00000000000000 #ifndef DB_SQLITE2_H #define DB_SQLITE2_H /* public */ int db_sqlite2_open(const char *file, db_conn_p *conn_p); /* Private, should not be here */ void db_sqlite2_close(db_conn_p db_conn); int db_sqlite2_exec(db_conn_p conn, const char *sql); int db_sqlite2_prepare(db_conn_p conn, const char *sql, db_stmt_p *stmt_p, char **pptail); int db_sqlite2_stmt_next(db_stmt_p stmt); const char* db_sqlite2_stmt_get_column_text(db_stmt_p stmt, int column); const void* db_sqlite2_stmt_get_column_blob(db_stmt_p stmt, int column); int db_sqlite2_stmt_get_column_int(db_stmt_p stmt, int column); int db_sqlite2_stmt_close(db_stmt_p stmt); int db_sqlite2_upgrade_to_schema(int version); #endif csync2-2.0-22-gce67c55/debian/000077500000000000000000000000001333746731400154605ustar00rootroot00000000000000csync2-2.0-22-gce67c55/debian/README.Debian000066400000000000000000000007521333746731400175250ustar00rootroot00000000000000 csync2 for Debian ----------------- You need to create an SSL certificate for the local Csync2 server. You can create a certificate using the following commands: openssl genrsa -out /etc/csync2_ssl_key.pem 1024 openssl req -new -key /etc/csync2_ssl_key.pem -out /etc/csync2_ssl_cert.csr openssl x509 -req -days 600 -in /etc/csync2_ssl_cert.csr \ -signkey /etc/csync2_ssl_key.pem -out /etc/csync2_ssl_cert.pem -- Michael Prokop , Fri, 23 Sep 2005 12:11:25 +0200 csync2-2.0-22-gce67c55/debian/changelog000066400000000000000000000124231333746731400173340ustar00rootroot00000000000000csync2 (2.0-1) unstable; urgency=low * Preparing new Upstream Version -- Lars Ellenberg Mon, 09 Mar 2015 15:06:30 +0100 csync2 (2.0~0rc1) unstable; urgency=low * Preparing new Upstream Version -- Lars Ellenberg Mon, 06 Dec 2010 20:02:25 +0100 csync2 (1.34-2) unstable; urgency=low * sqlite3 integration -- Ard van Breemen Wed, 18 Jun 2008 13:13:22 +0200 csync2 (1.34-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 24 Jul 2007 23:04:14 +0200 csync2 (1.33-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 08 Aug 2006 19:46:49 +0200 csync2 (1.32-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Wed, 19 Apr 2006 12:49:28 +0200 csync2 (1.31-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Mon, 03 Apr 2006 16:07:42 +0200 csync2 (1.30-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 02 Feb 2006 13:47:54 +0100 csync2 (1.29-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Mon, 02 Jan 2006 16:31:01 +0100 csync2 (1.28-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 22 Nov 2005 15:01:56 +0100 csync2 (1.27-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Fri, 18 Nov 2005 15:16:16 +0100 csync2 (1.26-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 15 Nov 2005 20:29:52 +0100 csync2 (1.25-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Fri, 11 Nov 2005 15:26:07 +0100 csync2 (1.24-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 18 Oct 2005 14:12:50 +0200 csync2 (1.23-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 11 Oct 2005 17:42:10 +0200 csync2 (1.22-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Mon, 19 Sep 2005 13:17:43 +0200 csync2 (1.21-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 23 Aug 2005 15:43:47 +0200 csync2 (1.20-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 04 Aug 2005 11:44:01 +0200 csync2 (1.19-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 14 Jun 2005 11:28:00 +0200 csync2 (1.18-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Fri, 03 Jun 2005 18:01:06 +0200 csync2 (1.17-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Mon, 30 May 2005 19:07:13 +0200 csync2 (1.16-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Wed, 13 Oct 2004 12:04:18 +0200 csync2 (1.15-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Tue, 06 Jul 2004 17:33:36 +0200 csync2 (1.14-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Mon, 14 Jun 2004 11:58:18 +0200 csync2 (1.13-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 13 May 2004 12:50:36 +0100 csync2 (1.12-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 27 Apr 2004 12:50:36 +0100 csync2 (1.11-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 22 Apr 2004 16:31:51 +0100 csync2 (1.10-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 22 Apr 2004 16:18:31 +0100 csync2 (1.9-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 21 Apr 2004 15:35:22 +0100 csync2 (1.8-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 21 Apr 2004 14:07:34 +0100 csync2 (1.7-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 8 Apr 2004 12:29:17 +0100 csync2 (1.6-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Wed, 7 Apr 2004 14:38:30 +0100 csync2 (1.5-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Mon, 5 Apr 2004 18:27:10 +0100 csync2 (1.4-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Fri, 2 Apr 2004 15:34:50 +0100 csync2 (1.3-1) unstable; urgency=low * New Upstream Version. -- Clifford Wolf Thu, 1 Apr 2004 14:44:45 +0100 csync2 (1.2-1) unstable; urgency=low * New Upstream Version. -- Philipp Richter Tue, 30 Mar 2004 13:20:45 +0100 csync2 (1.1) unstable; urgency=low * Initial Release. -- Philipp Richter Fri, 26 Mar 2004 15:03:45 +0100 Local variables: mode: debian-changelog End: csync2-2.0-22-gce67c55/debian/compat000066400000000000000000000000021333746731400166560ustar00rootroot000000000000004 csync2-2.0-22-gce67c55/debian/control000066400000000000000000000010601333746731400170600ustar00rootroot00000000000000Source: csync2 Section: admin Priority: optional Maintainer: Philipp Richter Build-Depends: debhelper (>= 4.0.0), librsync-dev, libsqlite3-dev, libc6-dev, libgnutls-dev, bison, flex Standards-Version: 3.6.2 Package: csync2 Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Suggests: sqlite Description: cluster synchronization tool CSYNC2 synchronizes files in a cluster using the rsync-algorithm. It maintains a database of modified files so it is able to handle deletion of files and file modification conflicts. csync2-2.0-22-gce67c55/debian/copyright000066400000000000000000000011251333746731400174120ustar00rootroot00000000000000This package was first debianized by Philipp Richter on Fri, 26 Mar 2004 15:03:45 +0100. Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH http://www.linbit.com; see also AUTHORS License: Csync2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. csync2-2.0-22-gce67c55/debian/cron.d000066400000000000000000000017631333746731400165750ustar00rootroot00000000000000# # Test if this synchronization pair is in sync. # # csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # # 0 4 * * * root csync2 -cr / ; csync2 -T csync2-2.0-22-gce67c55/debian/dirs000066400000000000000000000000551333746731400163440ustar00rootroot00000000000000etc usr/sbin var/lib/csync2 usr/share/csync2 csync2-2.0-22-gce67c55/debian/docs000066400000000000000000000000071333746731400163300ustar00rootroot00000000000000README csync2-2.0-22-gce67c55/debian/overrides000066400000000000000000000001121333746731400173770ustar00rootroot00000000000000csync2: executable-not-elf-or-script ./usr/share/csync2/csync2_locheck.sh csync2-2.0-22-gce67c55/debian/postinst000066400000000000000000000023471333746731400172740ustar00rootroot00000000000000#! /bin/sh # postinst script for csync2 # # see: dh_installdeb(1) # set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package # case "$1" in configure) if ! grep -q "^csync2" /etc/services ; then echo "csync2 30865/tcp" >>/etc/services fi if ! grep -q "^csync2" /etc/inetd.conf ; then update-inetd --remove '^csync2' update-inetd --group OTHER --add \ 'csync2\t\tstream\ttcp\tnowait\troot\t/usr/sbin/csync2\tcsync2 -i' fi ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 csync2-2.0-22-gce67c55/debian/prerm000066400000000000000000000022351333746731400165320ustar00rootroot00000000000000#! /bin/sh # prerm script for csync2 # # see: dh_installdeb(1) # set -e # summary of how this script can be called: # * `remove' # * `upgrade' # * `failed-upgrade' # * `remove' `in-favour' # * `deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in remove|upgrade|deconfigure) if grep -q "^csync2" /etc/services ; then grep -v "^csync2" /etc/services >/etc/services.new mv -f /etc/services.new /etc/services fi update-inetd --remove '^csync2' if [ -f /var/run/inetd.pid ] ; then kill -s HUP cat /var/run/inetd.pid 2>/dev/null fi ;; failed-upgrade) ;; *) echo "prerm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 csync2-2.0-22-gce67c55/debian/rules000077500000000000000000000034131333746731400165410ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 CFLAGS = -Wall -g -I /usr/include/postgresql ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) INSTALL_PROGRAM += -s endif configure: configure-stamp configure-stamp: dh_testdir ./configure CFLAGS="$(CFLAGS)" --prefix=/usr --localstatedir=/var --sysconfdir=/etc --enable-mysql --enable-postgres --enable-sqlite3 touch configure-stamp build: build-stamp build-stamp: configure-stamp dh_testdir $(MAKE) touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs install csync2 $(CURDIR)/debian/csync2/usr/sbin/csync2 install csync2-compare $(CURDIR)/debian/csync2/usr/sbin/csync2-compare install csync2_locheck.sh $(CURDIR)/debian/csync2/usr/share/csync2/csync2_locheck.sh install -m 644 csync2.cfg $(CURDIR)/debian/csync2/etc/csync2.cfg # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installchangelogs dh_installdocs dh_installman csync2.1 dh_installcron dh_link dh_strip dh_compress mkdir -p $(CURDIR)/debian/csync2/usr/share/lintian/overrides/ cp -av debian/overrides $(CURDIR)/debian/csync2/usr/share/lintian/overrides/csync2 dh_fixperms dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure csync2-2.0-22-gce67c55/dl.h000066400000000000000000000003771333746731400150150ustar00rootroot00000000000000#ifndef DL_H #define DL_H #include #define LOOKUP_SYMBOL(dl_handle, sym) \ f.sym ## _fn = dlsym(dl_handle, #sym); \ if ((f.sym ## _fn) == NULL) { \ csync_fatal ("Could not lookup %s in shared library: %s\n", #sym, dlerror()); \ } #endif csync2-2.0-22-gce67c55/doc/000077500000000000000000000000001333746731400150035ustar00rootroot00000000000000csync2-2.0-22-gce67c55/doc/csync2_paper.tex000066400000000000000000001151001333746731400201130ustar00rootroot00000000000000\documentclass[a4paper,twocolumn]{article} \usepackage{nopageno} \def\csync2{{\sc Csync$^{2}$}} \begin{document} \title{Cluster synchronization with \csync2} \author{Original Author: Clifford Wolf for LINBIT\\ Developed and maintained by LINBIT, http://www.linbit.com/} \maketitle \section{Introduction} \csync2 [1] is a tool for asynchronous file synchronization in clusters. Asynchronous file synchronization is good for files which are seldom modified - such as configuration files or application images - but it is not adequate for some other types of data. For instance a database with continuous write accesses should be synced synchronously in order to ensure the data integrity. But that does not automatically mean that synchronous synchronization is better; it simply is different and there are many cases where asynchronous synchronization is favored over synchronous synchronization. Some pros of asynchronous synchronization are: {\bf 1.} Most asynchronous synchronization tools (including \csync2) are implemented as single-shot commands which need to be executed each time in order to run one synchronization cycle. Therefore it is possible to test changes on one host before deploying them on the others (and also return to the old state if the changes turn out to be bogus). {\bf 2.} The synchronization algorithms are much simpler and thus less error-prone. {\bf 3.} Asynchronous synchronization tools can be (and usually are) implemented as normal user mode programs. Synchronous synchronization tools need to be implemented as operating system extensions. Therefore asynchronous tools are easier to deploy and more portable. {\bf 4.} It is much easier to build systems which allow setups with many hosts and complex replication rules. But most asynchronous synchronization tools are pretty primitive and do not even cover a small portion of the issues found in real world environments. I have developed \csync2 because I found none of the existing tools for asynchronous synchronization satisfying. The development of \csync2 has been sponsored by LINBIT Information Technologies [2], the company which also sponsors the synchronous block device synchronization toolchain DRBD [3]. \hspace{0.2cm} Note: I will simply use the term {\it synchronization} instead of the semi-oxymoron {\it asynchronous synchronization} in the rest of this paper. \subsection{\csync2 features} Most synchronization tools are very simple wrappers for remote-copy tools such as {\tt rsync} or {\tt scp}. These solutions work well in most cases but still leave a big gap for more sophisticated tools such as \csync2. The most important features of \csync2 are described in the following sections. \subsubsection{Conflict detection} \label{confl_detect} Most of the trivial synchronization tools just copy the newer file over the older one. This can be a very dangerous behavior if the same file has been changed on more than one host. \csync2 detects such a situation as a conflict and will not synchronize the file. Those conflicts then need to be resolved manually by the cluster administrator. It is not considered as a conflict by \csync2 when the same change has been performed on two hosts (e.g. because it has already been synchronized with another tool). It is also possible to let \csync2 resolve conflicts automatically for some or all files using one of the pre-defined auto-resolve methods. The available methods are: {\tt none} (the default behavior), {\tt first} (the host on which \csync2 is executed first wins), {\tt younger} and {\tt older} (the younger or older file wins), {\tt bigger} and {\tt smaller} (the bigger or smaller file wins), {\tt left} and {\tt right} (the host on the left side or the right side in the host list wins). The {\tt younger}, {\tt older}, {\tt bigger} and {\tt smaller} methods let the remote side win the conflict if the file has been removed on the local side. \subsubsection{Replicating file removals} Many synchronization tools can not synchronize file removals because they can not distinguish between the file being removed on one host and being created on the other one. So instead of removing the file on the second host they recreate it on the first one. \csync2 detects file removals as such and can synchronize them correctly. \subsubsection{Complex setups} Many synchronization tools are strictly designed for two-host-setups. This is an inadequate restriction and so \csync2 can handle any number of hosts. \csync2 can even handle complex setups where e.g. all hosts in a cluster share the {\tt /etc/hosts} file, but one {\tt /etc/passwd} file is only shared among the members of a small sub-group of hosts and another {\tt /etc/passwd} file is shared among the other hosts in the cluster. \subsubsection{Reacting to updates} In many cases it is not enough to simply synchronize a file between cluster nodes. It also is important to tell the applications using the synchronized file that the underlying file has been changed, e.g. by restarting the application. \csync2 can be configured to execute arbitrary commands when files matching an arbitrary set of shell patterns are synchronized. \section{The \csync2 algorithm} Many other synchronization tools compare the hosts, try to figure out which host is the most up-to-date one and then synchronize the state from this host to all other hosts. This algorithm can not detect conflicts, can not distinguish between file removals and file creations and therfore it is not used in \csync2. \csync2 creates a little database with filesystem metadata on each host. This database ({\tt /var/lib/csync2/{\it hostname}.db}) contains a list of the local files under the control of \csync2. The database also contains information such as the file modification timestamps and file sizes. This database is used by \csync2 to detect changes by comparison with the local filesystem. The synchronization itself is then performed using the \csync2 protocol (TCP port 30865). Note that this approach implies that \csync2 can only push changes from the machine on which the changes has been performed to the other machines in the cluster. Running \csync2 on any other machine in the cluster can not detect and so can not synchronize the changes. Librsync [4] is used for bandwidth-saving file synchronization and SSL is used for encrypting the network traffic. The sqlite library [5] (version 2) is used for managing the \csync2 database files. Authentication is performed using auto-generated pre-shared-keys in combination with the peer IP address and the peer SSL certificate. \section{Setting up \csync2} \subsection{Building \csync2 from source} Simply download the latest \csync2 source tar.gz from {\bf \tt http://oss.linbit.com/csync2/}, extract it and run the usual {\tt ./configure} - {\tt make} - {\tt make install} trio. \csync2 has a few prerequisites in addition to a C compiler, the standard system libraries and headers and the usual gnu toolchain ({\tt make}, etc): {\bf 1.} You need librsync, libsqlite (version 2) and gnutls installed (including development headers). {\bf 2.} Bison and flex are needed to build the configuration file parser. \subsection{\csync2 in Linux distributions} As of November 2011, all major linux distributions have some \csync2 1.34 package available, but to upgrade to an up-to-date \csync2 version, you probably need to wait a bit or build the package from source. The \csync2 source package contains an RPM {\tt .spec} file as well as a {\tt debian/} directory. So it is possible to use {\tt rpmbuild} or {\tt debuild} to build \csync2. \subsection{Post installation} Next you need to create an SSL certificate for the local \csync2 server. Simply running {\tt make cert} in the \csync2 source directory will create and install a self-signed SSL certificate for you. Alternatively, if you have no source around, run the following commands: \begin{verbatim} openssl genrsa \ -out /etc/csync2_ssl_key.pem 1024 openssl req -new \ -key /etc/csync2_ssl_key.pem \ -out /etc/csync2_ssl_cert.csr openssl x509 -req -days 600 \ -in /etc/csync2_ssl_cert.csr \ -signkey /etc/csync2_ssl_key.pem \ -out /etc/csync2_ssl_cert.pem \end{verbatim} You have to do that on each host you're running csync2 on. When servers are talking with each other for the first time, they add each other to the database. The \csync2 TCP port 30865 needs to be added to the {\tt /etc/services} file and inetd needs to be told about \csync2 by adding \begin{verbatim} csync2 stream tcp nowait root \ /usr/local/sbin/csync2 csync2 -i \end{verbatim} to {\tt /etc/inetd.conf}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{figure*}[t] \begin{center} \begin{verbatim} group mygroup # A synchronization group (see 3.4.1) { host host1 host2 (host3); # host list (see 3.4.2) host host4@host4-eth2; key /etc/csync2.key_mygroup; # pre-shared-key (see 3.4.3) include /etc/apache; # include/exclude patterns (see 3.4.4) include %homedir%/bob; exclude %homedir%/bob/temp; exclude *~ .*; action # an action section (see 3.4.5) { pattern /etc/apache/httpd.conf; pattern /etc/apache/sites-available/*; exec "/usr/sbin/apache2ctl graceful"; logfile "/var/log/csync2_action.log"; do-local; # do-local-only; } backup-directory /var/backups/csync2; backup-generations 3; # backup old files (see 3.4.11) auto none; # auto resolving mode (see 3.4.6) } prefix homedir # a prefix declaration (see 3.4.7) { on host[12]: /export/users; on *: /home; } \end{verbatim} \end{center} \caption{Example \csync2 configuration file} \end{figure*} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{figure*}[t] \begin{center} \begin{verbatim} csync2 -cr / if csync2 -M; then echo "!!" echo "!! There are unsynced changes! Type 'yes' if you still want to" echo "!! exit (or press crtl-c) and anything else if you want to start" echo "!! a new login shell instead." echo "!!" if read -p "Do you really want to logout? " in && [ ".$in" != ".yes" ]; then exec bash --login fi fi \end{verbatim} \end{center} \caption{The {\tt csync2\_locheck.sh} script} \end{figure*} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Configuration File} Figure 1 shows a simple \csync2 configuration file. The configuration filename is {\tt /etc/csync2.cfg} when no {\tt -C {\it configname}} option has been passed and {\tt /etc/csync2\_{\it configname}.cfg} with a {\tt -C {\it configname}} option. \subsubsection{Synchronization Groups} In the example configuration file you will find the declaration of a synchronization group called {\tt mygroup}. A \csync2 setup can have any number of synchronization groups. Each group has its own list of member hosts and include/exclude rules. \csync2 automatically ignores all groups which do not contain the local hostname in the host list. This way you can use one big \csync2 configuration file for the entire cluster. \subsubsection{Host Lists} Host lists are specified using the {\tt host} keyword. You can eighter specify the hosts in a whitespace seperated list or use an extra {\tt host} statement for each host. The hostnames used here must be the local hostnames of the cluster nodes. That means you must use exactly the same string as printed out by the {\tt hostname} command. Otherwise csync2 would be unable to associate the hostnames in the configuration file with the cluster nodes. The {\tt -N \it hostname} command line option can be used to set the local hostname used by \csync2 to a different value than the one provided by the {\tt hostname} command. This may be e.g. useful for environments where the local hostnames are automatically set by a DHCP server and because of that change often. Sometimes it is desired that a host is receiving \csync2 connections on an IP address which is not the IP address its DNS entry resolves to, e.g.~when a crossover cable is used to directly connect the hosts or an extra synchronization network should be used. In this cases the syntax {\tt{\it hostname}@{\it interfacename}} has to be used for the {\tt host} records (see {\tt host4} in the example config file). Sometimes a host shall only receive updates from other hosts in the synchronization group but shall not be allowed to send updates to the other hosts. Such hosts (so-called {\it slave hosts}) must be specified in brackets, such as {\tt host3} in the example config file. \subsubsection{Pre-Shared-Keys} Authentication is performed using the IP addresses and pre-shared-keys in \csync2. Each synchronization group in the config file must have exactly one {\tt key} record specifying the file containing the pre-shared-key for this group. It is recommended to use a separate key for each synchronization group and only place a key file on those hosts which actually are members in the corresponding synchronization group. The key files can be generated with {\tt csync2 -k {\it filename}}. \subsubsection{Include/Exclude Patterns} The {\tt include} and {\tt exclude} patterns are used to specify which files should be synced in the synchronization group. There are two kinds of patterns: pathname patterns which start with a slash character (or a prefix such as the {\tt \%homedir\%} in the example; prefixes are explained in a later section) and basename patterns which do not. The last matching pattern for each of both categories is chosen. If both categories match, the file will be synchronized. The pathname patterns are matched against the beginning of the filename. So they must either match the full absolute filename or must match a directory in the path to the file. The file will not be synchronized if no matching {\tt include} or {\tt exclude} pathname pattern is found (i.e. the default pathname pattern is an exclude pattern). The basename patterns are matched against the base filename without the path. So they can e.g. be used to include or exclude files by their filename extensions. The default basename pattern is an include pattern. In our example config file that means that all files from {\tt /etc/apache} and {\tt \%homedir\%/bob} are synced, except the dot files, files with a tilde character at the end of the filename, and files from {\tt \%homedir\%/bob/temp}. \subsubsection{Actions} Each synchronization group may have any number of {\tt action} sections. These {\tt action} sections are used to specify shell commands which should be executed after a file is synchronized that matches any of the specified patterns. The {\tt exec} statement is used to specify the command which should be executed. Note that if multiple files matching the pattern are synced in one run, this command will only be executed once. The special token {\tt \%\%} in the command string is substituted with the list of files which triggered the command execution. The output of the command is appended to the specified logfile, or to {\tt /dev/null} if the {\tt logfile} statement is omitted. Usually the action is only triggered on the targed hosts, not on the host on which the file modification has been detected in the first place. The {\tt do-local} statement can be used to change this behavior and let \csync2 also execute the command on the host from which the modification originated. You can use {\tt do-local-only} to execute the action only on this machine. \subsubsection{Conflict Auto-resolving} The {\tt auto} statement is used to specify the conflict auto-resolving mechanism for this synchronization group. The default value is {\tt auto none}. See section \ref{confl_detect} for a list of possible values for this setting. \subsubsection{Prefix Declarations} Prefixes (such as the {\tt \%homedir\%} prefix in the example configuration file) can be used to synchronize directories which are named differently on the cluster nodes. In the example configuration file the directory for the user home directories is {\tt /export/users} on the hosts {\tt host1} and {\tt host2} and {\tt /home} on the other hosts. The prefix value must be an absolute path name and must not contain any wildcard characters. \subsubsection{The {\tt nossl} statement} Usually all \csync2 network communication is encrypted using SSL. This can be changed with the {\tt nossl} statement. This statement may only occur in the root context (not in a {\tt group} or {\tt prefix} section) and has two parameters. The first one is a shell pattern matching the source DNS name for the TCP connection and the second one is a shell pattern matching the destination DNS name. So if e.g.~a secure synchronization network is used between some hosts and all the interface DNS names end with {\tt -sync}, a simple \begin{verbatim} nossl *-sync *-sync; \end{verbatim} will disable the encryption overhead on the synchronization network. All other traffic will stay SSL encrypted. \subsubsection{The {\tt config} statement} The {\tt config} statement is nothing more then an include statement and can be used to include other config files. This can be used to modularize the configuration file. \subsubsection{The {\tt ignore} statement} The {\tt ignore} statement can be used to tell \csync2 to not check and not sync the file user-id, the file group-id and/or the file permissions. The statement is only valid in the root context and accepts the parameters {\tt uid}, {\tt gid} and {\tt mod} to turn off handling of user-ids, group-ids and file permissions. \subsubsection{The {\tt tempdir} statement} Preferably don't use this setting. The {\tt tempdir} statement specifies the directory to be used for temporary files while receiving data through librsync. \csync2 will try to create temporary files in {\tt tempdir} if specified, in the same directory as the currently processed file, in the directory given by the {\tt TMPDIR} environment variable, the system default {\tt P\_tmpdir}, or {\tt /tmp}, in that order. This implies that if you specify a {\tt tempdir} which is not on the same file system as the processed files, it will be impossible to rename the patched files in place, and \csync2 will fall back to truncate and copy. Which can in various failure scenarios result in corrupted (partially transfered, truncated) files on the destination. \subsubsection{The {\tt lock-timeout} statement} The {\tt lock-timeout} statement specifies the seconds to wait wor a database lock before giving up. Default is 12 seconds. The amount will be slightly randomized with a jitter of up to 6 seconds based on the respective process id. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{figure*}[t] \begin{center} \begin{verbatim} CREATE TABLE file ( filename, checktxt, UNIQUE ( filename ) ON CONFLICT REPLACE ); CREATE TABLE dirty ( filename, force, myname, peername, UNIQUE ( filename, peername ) ON CONFLICT IGNORE ); CREATE TABLE hint ( filename, recursive, UNIQUE ( filename, recursive ) ON CONFLICT IGNORE ); CREATE TABLE action ( filename, command, logfile, UNIQUE ( filename, command ) ON CONFLICT IGNORE ); CREATE TABLE x509_cert ( peername, certdata, UNIQUE ( peername ) ON CONFLICT IGNORE ); \end{verbatim} \end{center} \caption{The \csync2 database schema} \end{figure*} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{Backing up} \csync2 can back up the files it modifies. This may be useful for scenarios where one is afraid of accidentally syncing files in the wrong direction. The {\tt backup-directory} statement is used to tell \csync2 in which directory it should create the backup files and the {\tt backup-generations} statement is used to tell \csync2 how many old versions of the files should be kept in the backup directory. The files in the backup directory are named like the file they back up, with all slashes substituted by underscores and a generation counter appended. Note that only the file content, not the metadata such as ownership and permissions are backed up. Per default \csync2 does not back up the files it modifies. The default value for {\tt backup-generations} is {\tt 3}. \subsection{Activating the Logout Check} The \csync2 sources contain a little script called {\tt csync2\_locheck.sh} (Figure 2). If you copy that script into your {\tt \textasciitilde/.bash\_logout} script (or include it using the {\tt source} shell command), the shell will not let you log out if there are any unsynced changes. \section{Database Schema} Figure 3 shows the \csync2 database schema. The database can be accessed using the {\tt sqlite} command line shell. All string values are URL encoded in the database. The {\tt file} table contains a list of all local files under \csync2 control, the {\tt checktxt} attribute contains a special string with information about file type, size, modification time and more. It looks like this: \begin{verbatim} v1:mtime=1103471832:mode=33152: uid=1001:gid=111:type=reg:size=301 \end{verbatim} This {\tt checktxt} attribute is used to check if a file has been changed on the local host. If a local change has been detected, the entry in the {\tt file} table is updated and entries in the {\tt dirty} table are created for all peer hosts which should be updated. This way the information that a host should be updated does not get lost, even if the host in question is unreachable right now. The {\tt force} attribute is set to {\tt 0} by default and to {\tt 1} when the cluster administrator marks one side as the right one in a synchronization conflict. The {\tt hint} table is usually not used. In large setups this table can be filled by a daemon listening on the inotify API. It is possible to tell \csync2 to not check all files it is responsible for but only those which have entries in the {\tt hint} table. However, the Linux syscall API is so fast that this only makes sense for really huge setups. The {\tt action} table is used for scheduling actions. Usually this table is empty after \csync2 has been terminated. However, it is possible that \csync2 gets interrupted in the middle of the synchronization process. In this case the records in the {\tt action} table are processed when \csync2 is executed the next time. The {\tt x509\_cert} table is used to cache the SSL cetrificates used by the other hosts in the csync2 cluster (like the SSH {\tt known\_hosts} file). \section{Running \csync2} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{figure*}[t] \begin{center} \begin{tabular}{|p{0.5\linewidth}|p{0.5\linewidth}|} \hline \begin{tiny} \begin{verbatim} csync2 2.0 - cluster synchronization tool, 2nd generation Copyright (C) 2004 - 2017 LINBIT Information Technologies GmbH http://www.linbit.com See also: http://git.linbit.com/csync2.git/?a=blob;f=AUTHORS Version: 2.0 This program is free software under the terms of the GNU GPL. Usage: ./csync2 [-v..] [-C config-name] \ [-D database-dir] [-N hostname] [-p port] .. With file parameters: -h [-r] file.. Add (recursive) hints for check to db -c [-r] file.. Check files and maybe add to dirty db -u [-d] [-r] file.. Updates files if listed in dirty db -o [-r] file.. Create list of files in compare-mode -f [-r] file.. Force files to win next conflict resolution -m file.. Mark files in database as dirty Simple mode: -x [-d] [[-r] file..] Run checks for all given files and update remote hosts. Without file parameters: -c Check all hints in db and eventually mark files as dirty -u [-d] Update (transfer dirty files to peers and mark as clear) -H List all pending hints from status db -L List all file-entries from status db -M List all dirty files from status db -S myname peername List file-entries from status db for this synchronization pair. -T Test if everything is in sync with all peers. -T filename Test if this file is in sync with all peers. -T myname peername Test if this synchronization pair is in sync. -T myname peer file Test only this file in this sync pair. -TT As -T, but print the unified diffs. -i Run in inetd server mode. -ii Run in stand-alone server mode. -iii Run in stand-alone server mode (one connect only). -l Send some messages to syslog instead of stderr to not clobber the protocol in case stdout and stderr point to the same fd. Default for inetd mode. -R Remove files from database which do not match config entries. Exit codes: The modes -H, -L, -M and -S return 2 if the requested db is empty. The mode -T returns 2 if both hosts are in sync. Otherwise, only exit codes 0 (no errors) and 1 (some unspecified errrors) are expected. \end{verbatim} \end{tiny} & \begin{tiny} \begin{verbatim} Modifiers: -r Recursive operation over subdirectories -d Dry-run on all remote update operations -B Do not block everything into big SQL transactions. This slows down csync2 but allows multiple csync2 processes to access the database at the same time. Use e.g. when slow lines are used or huge files are transferred. -A Open database in asynchronous mode. This will cause data corruption if the operating system crashes or the computer loses power. -N address When running in stand-alone mode with -ii bind to a specific interface. You can pass either a hostname or ip address. If used, this value must match exactly the host value in each csync2.cfg file. -I Init-run. Use with care and read the documentation first! You usually do not need this option unless you are initializing groups with really large file lists. -X Also add removals to dirty db when doing a -TI run. -U Don't mark all other peers as dirty when doing a -TI run. -G Group1,Group2,Group3,... Only use these groups from config-file. -P peer1,peer1,... Only update these peers (still mark all as dirty). Only show files for these peers in -o (compare) mode. -F Add new entries to dirty database with force flag set. -t Print timestamps to debug output (e.g. for profiling). -s filename Print timestamps also to this file. -W fd Write a list of directories in which relevant files can be found to the specified file descriptor (when doing a -c run). The directory names in this output are zero-terminated. Database switches: -D database-dir or url default: /var/lib/csync2 Absolute path: use sqlite database in that directory URLs: sqlite:///some/path[/database.db3] sqlite3:///some/path[/database.db3] sqlite2:///some/path[/database.db] mysql://[:@]/[database] pgsql://[:@]/[database] If database is not given, it defaults to csync2_. Note that for non-sqlite backends, the database name is "cleaned", characters outside of [0-9][a-z][A-Z] will be replaced with _. Creating key file: csync2 -k filename Environment variables: CSYNC2_SYSTEM_DIR Directory containing csync2.cfg and other csync2 system files. Defaults to /etc. Csync2 will refuse to do anything if this file is found: $CSYNC2_SYSTEM_DIR/csync2.lock \end{verbatim} \end{tiny} \tabularnewline \hline \end{tabular} \end{center} \caption{The \csync2 help message} \end{figure*} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Simply calling {\tt csync2} without any additional arguments prints out a help message (Figure 4). A more detailed description of the most important usage scenarios is given in the next sections. \subsection{Just synchronizing the files} The command {\tt csync2 -x} (or {\tt csync2 -xv}) checks for local changes and tries to synchronize them to the other hosts. The option {\tt -d} (dry-run) can be used to do everything but the actual synchronization. When you start \csync2 the first time it compares its empty database with the filesystem and sees that all files just have been created. It then will try to synchronize the files. If the file is not present on the remote hosts it will simply be copied to the other host. There also is no problem if the file is already present on the remote host and has the same content. But if the file already exists on the remote host and has a different content, you have your first conflict. \subsection{Resolving a conflict} When two or more hosts in a \csync2 synchronization group have detected changes for the same file we run into a conflict: \csync2 can not know which version is the right one (unless an auto-resolving method has been specified in the configuration file). The cluster administrator needs to tell \csync2 which version is the correct one. This can be done using {\tt \csync2 -f}, e.g.: \begin{verbatim} # csync2 -x While syncing file /etc/hosts: ERROR from peer apollo: File is also marked dirty here! Finished with 1 errors. # csync2 -f /etc/hosts # csync2 -xv Connecting to host apollo (PLAIN) ... Updating /etc/hosts on apollo ... Finished with 0 errors. \end{verbatim} \subsection{Checking without syncing} It is also possible to just check the local filesystem without doing any connections to remote hosts: {\tt csync2 -cr /} (the {\tt -r} modifier tells \csync2 to do a recursive check). {\tt csync2 -c} without any additional parameters checks all files listed in the {\tt hints} table. The command {\tt csync2 -M} can be used to print the list of files marked dirty and therfore scheduled for synchronization. \subsection{Comparing the hosts} The {\tt csync2 -T} command can be used to compare the local database with the database of the remote hosts. Note that this command compares the databases and not the filesystems - so make sure that the databases are up-to-date on all hosts before running {\tt csync2 -T} and run {\tt csync2 -cr /} if you are unsure. The output of {\tt csync2 -T} is a table with 4 columns: {\bf 1.} The type of the found difference: {\tt X} means that the file exists on both hosts but is different, {\tt L} that the file is only present on the local host and {\tt R} that the file is only present on the remote host. {\bf 2.} The local interface DNS name (usually just the local hostname). {\bf 3.} The remote interface DNS name (usually just the remote hostname). {\bf 4.} The filename. The {\tt csync2 -TT {\it filename}} command can be used for displaying unified diffs between a local file and the remote hosts. \subsection{Bootstrapping large setups} The {\tt -I} option is a nice tool for bootstrapping larger \csync2 installations on slower networks. In such scenarios one usually wants to initially replicate the data using a more efficient way and then use \csync2 to synchronize the changes on a regular basis. The problem here is that when you start \csync2 the first time it detects a lot of newly created files and wants to synchronize them, just to find out that they are already in sync with the peers. The {\tt -I} option modifies the behavior of {\tt -c} so it only updates the {\tt file} table but does not create entries in the {\tt dirty} table. So you can simply use {\tt csync2 -cIr /} to initially create the \csync2 database on the cluster nodes when you know for sure that the hosts are already in sync. The {\tt -I} option may also be used with {\tt -T} to add the detected differences to the dirty table and so induce \csync2 to synchronize the local status of the files in question to the remote host. Usually {\tt -TI} does only schedule local files which do exist to the dirty database. That means that it does not induce \csync2 to remove a file on a remote host if it does not exist on the local host. That behavior can be changed using the {\tt -X} option. The files scheduled to be synced by {\tt -TI} are usually scheduled to be synced to all peers, not just the one peer which has been used in the {\tt -TI} run. This behavior can be changed using the {\tt -U} option. \subsection{Cleaning up the database} It can happen that old data is left over in the \csync2 database after a configuration change (e.g. files and hosts which are not referred anymore by the configuration file). Running {\tt csync2 -R} cleans up such old entries in the \csync2 database. \subsection{Multiple Configurations} Sometimes a higher abstracion level than simply having different synchronization groups is needed. For such cases it is possible to use multiple configuration files (and databases) side by side. The additional configurations must have a unique name. The configuration file is then named {\tt /etc/csync2\_{\it myname}.cfg} and the database is named {\tt /var/lib/csync2/{\it hostname}\_{\it myname}.db}. Accordingly \csync2 must be called with the {\tt -C {\it myname}} option. But there is no need for multiple \csync2 daemons. The \csync2 protocol allows the client to tell the server which configuration should be used for the current TCP connection. \section{Performance} In most cases \csync2 is used for syncing just some (up to a few hundred) system configuration files. In these cases all \csync2 calls are processed in less than one second, even on slow hardware. So a performance analysis is not interesting for these cases but only for setups where a huge amount of files is synced, e.g. when syncing entire application images with \csync2. A well-founded performance analysis which would allow meaningful comparisons with other synchronization tools would be beyond the scope of this paper. So here are just some quick and dirty numbers from a production 2-node cluster (2.40GHz dual-Xeon, 7200 RPM ATA HD, 1 GB Ram). The machines had an average load of 0.3 (web and mail) during my tests.. I have about 128.000 files (1.7 GB) of Linux kernel sources and object files on an ext3 filesystem under \csync2 control on the machines. Checking for changes ({\tt csync2 -cr /}) took 13.7 seconds wall clock time, 9.1 seconds in user mode and 4.1 seconds in kernel mode. The remaining 0.5 seconds were spent in other processes. Recreating the local database without adding the files to dirty table ({\tt csync2 -cIr} after removing the database file) took 28.5 seconds (18.6 sec user mode and 2.6 sec kernel mode). Comparing the \csync2 databases of both hosts ({\tt csync2 -T}) took 3 seconds wall clock time. Running {\tt csync2 -u} after adding all 128.000 files took 10 minutes wall clock time. That means that \csync2 tried to sync all 128.000 files and then recognized that the remote side had already the most up-to-date version of the file after comparing the checksums. All numbers are the average values of 10 iterations. \section{Security Notes} As statet earlier, authentication is performed using the peer IP address and a pre-shared-key. The traffic is SSL encrypted and the SSL certificate of the peer is checked when there has been already an SSL connection to that peer in the past (i.e.~the peer certificate is already cached in the database). All DNS names used in the \csync2 configuration file (the {\tt host} records) should be resolvable via the {\tt /etc/hosts} file to guard against DNS spoofing attacks. Depending on the list of files being managed by \csync2, an intruder on one of the cluster nodes can also modify the files under \csync2 control on the other cluster nodes and so might also gain access on them. However, an intruder can not modify any other files on the other hosts because \csync2 checks on the receiving side if all updates are OK according to the configuration file. For sure, an intruder would be able to work around this security checks when \csync2 is also used to sync the \csync2 configuration files. \csync2 only syncs the standard UNIX permissions (uid, gid and file mode). ACLs, Linux ext2fs/ext3fs attributes and other extended filesystem permissions are neither synced nor flushed (e.g. if they are set automatically when the file is created). On {\tt cygwin}, due to unresolved permission inheritance problems, no {\tt rename()} is attempted, but existing files are always truncated and then copied into from the temporary files. Suggestions for how to resolve that are most welcome. \section{Alternatives} \csync2 is not the only file synchronization tool. Some of the other free software file synchronization tools are: \subsection{Rsync} Rsync [6] is a tool for fast incremental file transfers, but is not a synchronization tool in the context of this paper. Actually \csync2 is using the rsync algorithm for file transfers. A variety of synchronization tools have been written on top of rsync. Most of them are tiny shell scripts. \subsection{Unison} Unison [7] is using an algorithm similar to the one used by \csync2, but is limited to two-host setups. Its focus is on interactive syncs (there even are graphical user interfaces) and it is targeting on syncing home directories between a laptop and a workstation. Unison is pretty intuitive to use, among other things because of its limitations. \subsection{Version Control Systems} Version control systems such as Subversion [8] can also be used to synchronize configuration files or application images. The advantage of version control systems is that they can do three way merges and preserve the entire history of a repository. The disadvantage is that they are much slower and require more disk space than plain synchronization tools. \section{References} {[1]} \csync2 \\ http://oss.linbit.com/csync2/ \\ http://git.linbit.com/csync2.git/ \\ git://git.linbit.com/csync2/ \medskip \\ {[2]} LINBIT Information Technologies \\ http://www.linbit.com/ \medskip \\ {[3]} DRBD \\ http://www.drbd.org/ \medskip \\ {[4]} Librsync \\ http://librsync.sourceforge.net/ \medskip \\ {[5]} SQLite \\ http://www.sqlite.org/ \medskip \\ {[6]} Rsync \\ http://samba.anu.edu.au/rsync/ \medskip \\ {[7]} Unison \\ http://www.cis.upenn.edu/\textasciitilde{}bcpierce/unison/ \medskip \\ {[8]} Subversion \\ http://subversion.tigris.org/ \end{document} csync2-2.0-22-gce67c55/error.c000066400000000000000000000067451333746731400155470ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include long csync_last_printtime = 0; FILE *csync_timestamp_out = 0; int csync_messages_printed = 0; time_t csync_startup_time = 0; void csync_printtime() { if (csync_timestamps || csync_timestamp_out) { time_t now = time(0); char ftbuffer[128]; if (!csync_startup_time) csync_startup_time = now; if (csync_last_printtime+300 < now) { csync_last_printtime = now; strftime(ftbuffer, 128, "%Y-%m-%d %H:%M:%S %Z (GMT%z)", localtime(&now)); if (csync_timestamp_out) fprintf(csync_timestamp_out, "<%d> TIMESTAMP: %s\n", (int)getpid(), ftbuffer); if (csync_timestamps) { if (csync_server_child_pid) fprintf(csync_debug_out, "<%d> ", csync_server_child_pid); fprintf(csync_debug_out, "TIMESTAMP: %s\n", ftbuffer); } } } } void csync_printtotaltime() { if (csync_timestamps || csync_timestamp_out) { time_t now = time(0); int seconds = now - csync_startup_time; csync_last_printtime = 0; csync_printtime(); if (csync_timestamp_out) fprintf(csync_timestamp_out, "<%d> TOTALTIME: %d:%02d:%02d\n", (int)getpid(), seconds / (60*60), (seconds/60) % 60, seconds % 60); if (csync_timestamps) { if (csync_server_child_pid) fprintf(csync_debug_out, "<%d> ", csync_server_child_pid); fprintf(csync_debug_out, "TOTALTIME: %d:%02d:%02d\n", seconds / (60*60), (seconds/60) % 60, seconds % 60); } } } void csync_printtime_prefix() { time_t now = time(0); char ftbuffer[32]; strftime(ftbuffer, 32, "%H:%M:%S", localtime(&now)); fprintf(csync_debug_out, "[%s] ", ftbuffer); } static int csync_log_level_to_sys_log_level(int lv) { return (lv < 0) ? LOG_ERR : (lv == 0) ? LOG_WARNING : (lv == 1) ? LOG_INFO : /* lv >= 2 */ LOG_DEBUG; } void csync_vdebug(int lv, const char *fmt, va_list ap) { if (csync_debug_level < lv) return; if (!csync_syslog) { csync_printtime(); if (csync_timestamps) csync_printtime_prefix(); if (csync_server_child_pid) fprintf(csync_debug_out, "<%d> ", csync_server_child_pid); vfprintf(csync_debug_out, fmt, ap); } else { vsyslog(csync_log_level_to_sys_log_level(lv), fmt, ap); } csync_messages_printed++; } void csync_fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); csync_vdebug(0, fmt, ap); va_end(ap); csync_db_close(); csync_last_printtime = 0; csync_printtime(); exit(1); } void csync_debug(int lv, const char *fmt, ...) { va_list ap; va_start(ap, fmt); csync_vdebug(lv, fmt, ap); va_end(ap); } csync2-2.0-22-gce67c55/getrealfn.c000066400000000000000000000074441333746731400163620ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include static char *my_get_current_dir_name() { #if defined __CYGWIN__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ char *r = malloc(1024); if (!getcwd(r, 1024)) strcpy(r, "/__PATH_TO_LONG__"); return r; #else return get_current_dir_name(); #endif } /* * glibc's realpath() is broken - so don't use it! */ char *getrealfn(const char *filename) { static char *ret = 0; char *st_mark = 0; struct stat st; char *tempfn; /* create working copy of filename */ tempfn = strdup(filename); /* make the path absolute */ if ( *tempfn != '/' ) { char *t2, *t1 = my_get_current_dir_name(); ASPRINTF(&t2, "%s/%s", t1, tempfn); free(t1); free(tempfn); tempfn = t2; } /* remove leading slashes from tempfn */ { char *tmp = tempfn + strlen(tempfn) - 1; while (tmp > tempfn && *tmp == '/') *(tmp--)=0; } /* get rid of the .. and // entries */ { char *source = tempfn, *target = tempfn; for (; *source; source++) { if ( *source == '/' ) { if ( *(source+1) == '/' ) continue; if ( !strncmp(source, "/../", 4) || !strcmp(source, "/..") ) { while (1) { if ( target == tempfn ) break; if ( *(--target) == '/' ) break; } source += 2; continue; } else if ( !strncmp(source, "/./", 3) ) { source += 2; } } *(target++) = *source; } *target = 0; } /* this case is trivial */ if ( !strcmp(tempfn, "/") ) goto return_filename; /* find the last stat-able directory element, but don't use the */ /* leaf-node because we do not want to resolve a symlink there. */ do { char *tmp = st_mark; st_mark = strrchr(tempfn, '/'); if ( tmp ) *tmp = '/'; assert( st_mark != 0 ); if ( st_mark == tempfn ) goto return_filename; *st_mark = 0; } while ( stat(tempfn, &st) || !S_ISDIR(st.st_mode) ); /* ok - this might be ugly, but who cares .. */ { int cwdfd = open(".", O_RDONLY); if (cwdfd < 0) csync_fatal("Cannot open current directory!\n"); if ( !chdir(tempfn) ) { char *t2, *t1 = my_get_current_dir_name(); if ( st_mark ) { ASPRINTF(&t2, "%s/%s", t1, st_mark+1); free(tempfn); free(t1); tempfn = t2; } else { free(tempfn); tempfn = t1; } if (fchdir(cwdfd)) csync_fatal("Cannot chdir back to previous directory!\n"); close(cwdfd); } else if ( st_mark ) *st_mark = '/'; } return_filename: /* remove a possible "/." from the end */ { int len = strlen(tempfn); if ( len >= 2 && !strcmp(tempfn+len-2, "/.") ) { if (len == 2) len++; *(tempfn+len-2) = 0; } } if (ret) free(ret); return (ret=tempfn); } #ifdef DEBUG_GETREALFN_MAIN /* debugging main function to debug this code stand-alone */ int main(int argc, char ** argv) { int i; for (i=1; i %s\n", argv[i], getrealfn(argv[i])); return 0; } #endif csync2-2.0-22-gce67c55/groups.c000066400000000000000000000136231333746731400157260ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include int csync_compare_mode = 0; int match_pattern_list( const char *filename, const char *basename, const struct csync_group_pattern *p) { int match_path = 0, match_base = 1; while (p) { int matched = 0; if ( p->iscompare && !csync_compare_mode ) goto next_pattern; if ( p->pattern[0] != '/' && p->pattern[0] != '%' ) { if ( !fnmatch(p->pattern, basename, 0) ) { match_base = p->isinclude; matched = 1; } } else { int fnm_pathname = p->star_matches_slashes ? 0 : FNM_PATHNAME; if ( !fnmatch(p->pattern, filename, FNM_LEADING_DIR|fnm_pathname) ) { match_path = p->isinclude; matched = 1; } } if ( matched ) { csync_debug(2, "Match (%c): %s on %s\n", p->isinclude ? '+' : '-', p->pattern, filename); } next_pattern: p = p->next; } return match_path && match_base; } const struct csync_group *csync_find_next( const struct csync_group *g, const char *file) { const char *basename = strrchr(file, '/'); if ( basename ) basename++; else basename = file; for (g = g==0 ? csync_group : g->next; g; g = g->next) { if ( !g->myname ) continue; if ( csync_compare_mode && !g->hasactivepeers ) continue; if ( match_pattern_list(file, basename, g->pattern) ) break; } return g; } int csync_step_into(const char *file) { const struct csync_group_pattern *p; const struct csync_group *g; if ( !strcmp(file, "/") ) return 1; for (g=csync_group; g; g=g->next) { if ( !g->myname ) continue; if ( csync_compare_mode && !g->hasactivepeers ) continue; for (p=g->pattern; p; p=p->next) { if ( p->iscompare && !csync_compare_mode ) continue; if ( (p->pattern[0] == '/' || p->pattern[0] == '%') && p->isinclude ) { char t[strlen(p->pattern)+1], *l; int fnm_pathname = p->star_matches_slashes ? 0 : FNM_PATHNAME; strcpy(t, p->pattern); while ( (l=strrchr(t, '/')) != 0 ) { *l = 0; if ( !fnmatch(t, file, fnm_pathname) ) return 1; } } } } return 0; } int csync_match_file(const char *file) { if ( csync_find_next(0, file) ) return 2; if ( csync_step_into(file) ) return 1; return 0; } void csync_check_usefullness(const char *file, int recursive) { struct csync_prefix *p = csync_prefix; if ( csync_find_next(0, file) ) return; if ( recursive && csync_step_into(file) ) return; if (*file == '/') while (p) { if (p->path) { int p_len = strlen(p->path); int f_len = strlen(file); /* p->path is some subtree of file */ if (p_len > f_len && !strncmp(p->path, file, f_len) && p->path[f_len] == '/') return; /* file is somewhere below p->path */ if (p_len <= f_len && !strncmp(p->path, file, p_len) && (file[p_len] == '/' || !file[p_len])) { char new_file[strlen(p->name) + strlen(file+p_len) + 10]; sprintf(new_file, "%%%s%%%s", p->name, file+p_len); if ( csync_find_next(0, new_file) ) return; if ( recursive && csync_step_into(new_file) ) return; } } p = p->next; } csync_debug(0, "WARNING: Parameter will be ignored: %s\n", file); } int csync_match_file_host(const char *file, const char *myname, const char *peername, const char **keys) { const struct csync_group *g = NULL; while ( (g=csync_find_next(g, file)) ) { struct csync_group_host *h = g->host; if ( strcmp(myname, g->myname) ) continue; if (keys) { const char **k = keys; while (*k && **k) if ( !strcmp(*(k++), g->key) ) goto found_key; continue; } found_key: while (h) { if ( !strcmp(h->hostname, peername) ) return 1; h = h->next; } } return 0; } struct peer *csync_find_peers(const char *file, const char *thispeer) { const struct csync_group *g = NULL; struct peer *plist = 0; int pl_size = 0; while ( (g=csync_find_next(g, file)) ) { struct csync_group_host *h = g->host; if (thispeer) { while (h) { if ( !strcmp(h->hostname, thispeer) ) break; h = h->next; } if (!h) goto next_group; h = g->host; } while (h) { int i=0; while (plist && plist[i].peername) if ( !strcmp(plist[i++].peername, h->hostname) ) goto next_host; plist = realloc(plist, sizeof(struct peer)*(++pl_size+1)); plist[pl_size-1].peername = h->hostname; plist[pl_size-1].myname = g->myname; plist[pl_size].peername = 0; next_host: h = h->next; } next_group: ; } return plist; } const char *csync_key(const char *hostname, const char *filename) { const struct csync_group *g = NULL; struct csync_group_host *h; while ( (g=csync_find_next(g, filename)) ) for (h = g->host; h; h = h->next) if (!strcmp(h->hostname, hostname)) return g->key; return 0; } int csync_perm(const char *filename, const char *key, const char *hostname) { const struct csync_group *g = NULL; struct csync_group_host *h; int false_retcode = 1; while ( (g=csync_find_next(g, filename)) ) { if ( !hostname ) continue; for (h = g->host; h; h = h->next) if (!strcmp(h->hostname, hostname) && !strcmp(g->key, key)) { if (!h->slave) return 0; else false_retcode = 2; } } return false_retcode; } csync2-2.0-22-gce67c55/prefixsubst.c000066400000000000000000000050111333746731400167550ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #define RINGBUFF_LEN 10 static char *ringbuff[RINGBUFF_LEN]; static int ringbuff_counter = 0; const char *prefixsubst(const char *in) { struct csync_prefix *p; const char *pn, *path; int pn_len; if (!in || *in != '%') return in; pn = in+1; pn_len = strcspn(pn, "%"); path = pn+pn_len; if (*path == '%') path++; for (p = csync_prefix; p; p = p->next) { if (strlen(p->name) == pn_len && !strncmp(p->name, pn, pn_len) && p->path) { ringbuff_counter = (ringbuff_counter+1) % RINGBUFF_LEN; if (ringbuff[ringbuff_counter]) free(ringbuff[ringbuff_counter]); ASPRINTF(&ringbuff[ringbuff_counter], "%s%s", p->path, path); return ringbuff[ringbuff_counter]; } } csync_fatal("Prefix '%.*s' is not defined for host '%s'.\n", pn_len, pn, myhostname); return 0; } const char *prefixencode(const char *filename) { #if __CYGWIN__ if (!strcmp(filename, "/")) { filename = "/cygdrive"; } #endif struct csync_prefix *p = csync_prefix; /* * Canonicalized paths will always contain / * Prefixsubsted paths will probably contain % */ if (*filename == '/') while (p) { if (p->path) { int p_len = strlen(p->path); int f_len = strlen(filename); if (p_len <= f_len && !strncmp(p->path, filename, p_len) && (filename[p_len] == '/' || !filename[p_len])) { ringbuff_counter = (ringbuff_counter+1) % RINGBUFF_LEN; if (ringbuff[ringbuff_counter]) free(ringbuff[ringbuff_counter]); ASPRINTF(&ringbuff[ringbuff_counter], "%%%s%%%s", p->name, filename+p_len); return ringbuff[ringbuff_counter]; } } p = p->next; } return filename; } csync2-2.0-22-gce67c55/release.sh000077500000000000000000000040331333746731400162150ustar00rootroot00000000000000#!/bin/bash # # csync2 - cluster synchronization tool, 2nd generation # Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH # http://www.linbit.com; see also AUTHORS # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # # Internal script for tagging a release # and creating the source tar file. PACKAGE=csync2 URL=http://git.linbit.com/csync2.git case "$1" in -*) echo "Usage: $0 newversion" ;; '') git ls-remote -t $URL ;; *) VERSION=${1%%-*} RELEASE=${1#*-} [[ $RELEASE = $VERSION ]] && RELEASE=1 set -ex which pdflatex LANG=C LC_ALL=C date "+csync2 ($VERSION-$RELEASE) unstable; urgency=low%n%n` ` * New Upstream Version.%n%n -- Lars Ellenberg ` ` %a, %d %b %Y ` `%H:%M:%S %z%n" > debian/changelog.new cat debian/changelog >> debian/changelog.new mv debian/changelog.new debian/changelog perl -pi -e "s/^AC_INIT.*/AC_INIT(csync2, $VERSION-$RELEASE, csync2\@lists.linbit.com)/" \ configure.ac perl -pi -e "s/^Version:.*/Version: $VERSION/;s/^Release:.*/Release: $RELEASE/" csync2.spec # # generate an uptodate copy of the paper # git commit -m "Preparing version $VERSION" \ # debian/changelog \ # configure.ac \ # csync2.spec # git tag -a -m "$PACKAGE-$VERSION" $PACKAGE-$VERSION # include paper.pdf in tarball # tar cvzf $PACKAGE-$VERSION.tar.gz \ # --owner=0 --group=0 $PACKAGE-$VERSION # rm -rf $PACKAGE-$VERSION ;; esac csync2-2.0-22-gce67c55/rsync.c000066400000000000000000000531531333746731400155470ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include /* for tmpfile replacement: */ #include #include #include /* for MAXPATHLEN */ #include #ifdef __CYGWIN__ #include #endif /* This has been taken from rsync:lib/compat.c */ /** * Like strncpy but does not 0 fill the buffer and always null * terminates. * * @param bufsize is the size of the destination buffer. * * @return index of the terminating byte. **/ #ifndef HAVE_STRLCPY static size_t strlcpy(char *d, const char *s, size_t bufsize) { size_t len = strlen(s); size_t ret = len; if (bufsize > 0) { if (len >= bufsize) len = bufsize-1; memcpy(d, s, len); d[len] = 0; } return ret; } #endif /* splits filepath at the last '/', if any, like so: * dirname basename filepath * "/" "" "/" * "/" "foo" "/foo" * no trailing slash in dirname, unless it is "/". * "/some/path" "" "/some/path/" * "/some/path" "foo" "/some/path/foo" * "" "foo" "foo" * * caller needs to supply enough room in dirname and basename * to hold the result, or NULL, if not interested. */ void split_dirname_basename(char *dirname, char* basename, const char *filepath) { const char *base = strrchr(filepath, '/'); size_t pathlen, dirlen, baselen; /* skip over last slash, if any. */ base = base ? base + 1 : filepath; pathlen = strlen(filepath); baselen = strlen(base); dirlen = pathlen - baselen; /* backtrack trailing slash(es) */ while (dirlen > 1 && filepath[dirlen-1] == '/') --dirlen; if (dirname) strlcpy(dirname, filepath, dirlen + 1); if (basename) strlcpy(basename, base, baselen + 1); } /* * Recursively creates the given path, with the given mode * Note that path argument is not directory name here but rather * a path to a file that you are going to create after calling mkpath(). * Works with relative paths as well. * Shamelessly copied from * Stackoverlow.com#http://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix * Returns: 0 on success and -1 on error */ int mkpath(const char *path, mode_t mode) { char temp[MAXPATHLEN]; char *remaining; if(!mode) { mode=S_IRWXU; } if(!path){ csync_debug(2,"invalid path"); return -1; } strlcpy(temp,path,strlen(path)); csync_debug(1,"mkpath full path: %s",temp); for( remaining=strchr(temp+1, '/'); remaining!=NULL; remaining=strchr(remaining+1, '/') ){ *remaining='\0'; if(mkdir(temp, mode)==-1) { //strchr keeps the parent in temp and child[ren] in remaining if(errno != EEXIST) { *remaining='/'; csync_debug(1,"error occured while creating path %s; cause : %s",temp,strerror(errno)); return -1; } } csync_debug(1,"mkdir parent dir: %s",temp); *remaining='/'; } return 0; } /* This has been taken from rsync sources: syscall.c */ #ifndef O_BINARY #define O_BINARY 0 #endif /* like mkstemp but forces permissions */ int do_mkstemp(char *template, mode_t perms) { perms |= S_IWUSR; #if defined HAVE_SECURE_MKSTEMP && defined HAVE_FCHMOD && (!defined HAVE_OPEN64 || defined HAVE_MKSTEMP64) { int fd = mkstemp(template); if (fd == -1) return -1; if (fchmod(fd, perms) != 0) { int errno_save = errno; close(fd); unlink(template); errno = errno_save; return -1; } #if defined HAVE_SETMODE && O_BINARY setmode(fd, O_BINARY); #endif return fd; } #else if (!mktemp(template)) return -1; return open(template, O_RDWR|O_EXCL|O_CREAT | O_BINARY, perms); #endif } /* define the order in which directories are tried when creating temp files */ static int next_tempdir(char **dir, unsigned int stage) { static char *dirs_to_try[] = { NULL /* csync_tempdir */, NULL /* stays NULL, same dir as input name */, NULL /* getenv("TMPDIR") */, P_tmpdir, "/tmp", }; static int n_dirs; int i; if (!n_dirs) { n_dirs = sizeof(dirs_to_try)/sizeof(dirs_to_try[0]); dirs_to_try[0] = csync_tempdir; dirs_to_try[2] = getenv("TMPDIR"); for (i = 0; i < n_dirs; i++) { struct stat sbuf; int ret; if (!dirs_to_try[i]) continue; if (!dirs_to_try[i][0]) { /* drop "" */ dirs_to_try[i] = NULL; continue; } ret = stat(dirs_to_try[i], &sbuf); if (ret || !S_ISDIR(sbuf.st_mode)) { csync_debug(1, "dropping tempdir candidate '%s': not a directory\n", dirs_to_try[i]); dirs_to_try[i] = NULL; } } } /* skip this stage, if previous stages have been equal. */ for (; stage < n_dirs; stage++) { if (dirs_to_try[stage] && !dirs_to_try[stage][0]) continue; for (i = 0; i < stage; i++) { if (dirs_to_try[i] == dirs_to_try[stage]) break; if (!dirs_to_try[i] || !dirs_to_try[stage]) continue; if (!strcmp(dirs_to_try[i], dirs_to_try[stage])) break; } if (i == stage) { *dir = dirs_to_try[stage]; return stage+1; } } return -1; } /* This has been taken from rsync sources: receiver.c, * and adapted: dropped the "make_unique" parameter, * as in our use case it is always false. * * Added tempdir parameter, * so we can try several dirs before giving up. */ #define TMPNAME_SUFFIX ".XXXXXX" #define TMPNAME_SUFFIX_LEN ((int)sizeof TMPNAME_SUFFIX - 1) /* get_tmpname() - create a tmp filename for a given filename * * If a tmpdir is defined, use that as the directory to put it in. Otherwise, * the tmp filename is in the same directory as the given name. Note that * there may be no directory at all in the given name! * * The tmp filename is basically the given filename with a dot prepended, and * .XXXXXX appended (for mkstemp() to put its unique gunk in). We take care * to not exceed either the MAXPATHLEN or NAME_MAX, especially the last, as * the basename basically becomes 8 characters longer. In such a case, the * original name is shortened sufficiently to make it all fit. * * Of course, the only reason the file is based on the original name is to * make it easier to figure out what purpose a temp file is serving when a * transfer is in progress. */ static int get_tmpname(char *fnametmp, const char *tempdir, const char *fname) { int maxname, length = 0; const char *f; char *suf; if (tempdir) { /* Note: this can't overflow, so the return value is safe */ length = strlcpy(fnametmp, tempdir, MAXPATHLEN - 2); fnametmp[length++] = '/'; } if ((f = strrchr(fname, '/')) != NULL) { ++f; if (!tempdir) { length = f - fname; /* copy up to and including the slash */ strlcpy(fnametmp, fname, length + 1); } } else f = fname; if (*f == '.') /* avoid an extra leading dot for OS X's sake */ f++; fnametmp[length++] = '.'; /* The maxname value is bufsize, and includes space for the '\0'. * NAME_MAX needs an extra -1 for the name's leading dot. */ maxname = MIN(MAXPATHLEN - length - TMPNAME_SUFFIX_LEN, NAME_MAX - 1 - TMPNAME_SUFFIX_LEN); if (maxname < 0) { csync_debug(1, "temporary filename too long: %s\n", fname); fnametmp[0] = '\0'; return 0; } if (maxname) { int added = strlcpy(fnametmp + length, f, maxname); if (added >= maxname) added = maxname - 1; suf = fnametmp + length + added; /* Trim any dangling high-bit chars if the first-trimmed char (if any) is * also a high-bit char, just in case we cut into a multi-byte sequence. * We are guaranteed to stop because of the leading '.' we added. */ if ((int)f[added] & 0x80) { while ((int)suf[-1] & 0x80) suf--; } /* trim one trailing dot before our suffix's dot */ if (suf[-1] == '.') suf--; } else suf = fnametmp + length - 1; /* overwrite the leading dot with suffix's dot */ memcpy(suf, TMPNAME_SUFFIX, TMPNAME_SUFFIX_LEN+1); return 1; } /* Returns open file handle for a temp file that resides in the * csync_tempdir (if specified), or * the same directory as fname. * If tempfile creation was not possible, before giving up, * TMPDIR, P_tmpdir, or /tmp are also tried, in that order. * The file must be removed after usage, or renamed into place. */ static FILE* open_temp_file(char *fnametmp, const char *fname) { FILE *f = NULL; char *dir = NULL; int fd; int i = 0; do { if (i > 0) csync_debug(3, "mkstemp %s failed: %s\n", fnametmp, strerror(errno)); i = next_tempdir(&dir, i); if (i < 0) return NULL; if (!get_tmpname(fnametmp, dir, fname)) return NULL; fd = do_mkstemp(fnametmp, S_IRUSR|S_IWUSR); } while (fd < 0 && errno == ENOENT); if (fd >= 0) { f = fdopen(fd, "wb+"); /* not unlinking since rename wouldn't work then */ } if (fd < 0 || !f) { csync_debug(1, "mkstemp %s failed: %s\n", fnametmp, strerror(errno)); return NULL; } return f; } /* FIXME ftell? long? seriously? * * Then again, it is only a sigfile, so the base file size would need to be * positively huge to have the size of the signature overflow a 32 bit LONG_MAX. * In which case csync2 would be the wrong tool anyways. */ void csync_send_file(FILE *in) { char buffer[512]; int rc, chunk; long size; fflush(in); size = ftell(in); rewind(in); conn_printf("octet-stream %ld\n", size); while ( size > 0 ) { chunk = size > 512 ? 512 : size; rc = fread(buffer, 1, chunk, in); if ( rc <= 0 ) csync_fatal("Read-error while sending data.\n"); chunk = rc; rc = conn_write(buffer, chunk); if ( rc != chunk ) csync_fatal("Write-error while sending data.\n"); size -= chunk; } } int csync_recv_file(FILE *out) { char buffer[512]; int rc, chunk; long size; if ( !conn_gets(buffer, 100) || sscanf(buffer, "octet-stream %ld\n", &size) != 1 ) { if (!strcmp(buffer, "ERROR\n")) { errno=EIO; return -1; } csync_fatal("Format-error while receiving data.\n"); } if (size < 0) { errno=EIO; return -1; } csync_debug(3, "Receiving %ld bytes ..\n", size); while ( size > 0 ) { chunk = size > 512 ? 512 : size; rc = conn_read(buffer, chunk); if ( rc <= 0 ) csync_fatal("Read-error while receiving data.\n"); chunk = rc; rc = fwrite(buffer, chunk, 1, out); if ( rc != 1 ) csync_fatal("Write-error while receiving data.\n"); size -= chunk; csync_debug(3, "Got %d bytes, %ld bytes left ..\n", chunk, size); } fflush(out); rewind(out); return 0; } /* * Return: * 0, *sig_file == NULL: base file does not exist, empty sig. * 0, *sig_file != NULL: sig_file contains the sig * -1, *sig_file == NULL: "IO Error" * -1, *sig_file != NULL: librsync error */ int csync_rs_sigfile(const char *filename, FILE **sig_file_out) { char tmpfname[MAXPATHLEN]; struct stat st; FILE *basis_file; FILE *sig_file = NULL; int r = -1; rs_result result; rs_stats_t stats; csync_debug(3, "Opening basis_file and sig_file for %s\n", filename); *sig_file_out = NULL; basis_file = fopen(prefixsubst(filename), "rb"); if (!basis_file && errno == ENOENT) { csync_debug(3, "Basis file does not exist.\n"); return 0; } if (!basis_file || fstat(fileno(basis_file), &st) || !S_ISREG(st.st_mode)) goto out; sig_file = open_temp_file(tmpfname, prefixsubst(filename)); if (!sig_file) goto out; if (unlink(tmpfname) < 0) goto out; csync_debug(3, "Running rs_sig_file() from librsync....\n"); /* see upstream * https://github.com/librsync/librsync/commit/152323729ac831727032daf50a10c1448b48f252 * as reaction to SECURITY: CVE-2014-8242 */ #ifdef RS_DEFAULT_STRONG_LEN result = rs_sig_file(basis_file, sig_file, RS_DEFAULT_BLOCK_LEN, RS_DEFAULT_STRONG_LEN, &stats); #else /* For backward compatibility, for now hardcode RS_MD4_SIG_MAGIC. * TODO: allow changing to RS_BLAKE2_SIG_MAGIC. */ result = rs_sig_file(basis_file, sig_file, RS_DEFAULT_BLOCK_LEN, 0, RS_MD4_SIG_MAGIC, &stats); #endif *sig_file_out = sig_file; sig_file = NULL; if (result != RS_DONE) csync_debug(0, "Internal error from rsync library!\n"); else r = 0; out: if (basis_file) fclose(basis_file); if (sig_file) fclose(sig_file); return r; } int csync_rs_check(const char *filename, int isreg) { FILE *sig_file = 0; char buffer1[512], buffer2[512]; int rc, chunk, found_diff = 0; int backup_errno; long size; long my_size = 0; csync_debug(3, "Csync2 / Librsync: csync_rs_check('%s', %d [%s])\n", filename, isreg, isreg ? "regular file" : "non-regular file"); csync_debug(3, "Reading signature size from peer....\n"); if (!conn_gets(buffer1, 100) || sscanf(buffer1, "octet-stream %ld\n", &size) != 1) csync_fatal("Format-error while receiving data.\n"); if (size < 0) { errno = EIO; goto io_error; } csync_debug(3, "Receiving %ld bytes ..\n", size); if (isreg) { if (csync_rs_sigfile(filename, &sig_file)) { if (!sig_file) goto io_error; goto error; } if (sig_file) { fflush(sig_file); my_size = ftell(sig_file); rewind(sig_file); } } if (size != my_size) { csync_debug(2, "Signature size differs: local=%d, peer=%d\n", my_size, size); found_diff = 1; } while (size > 0) { chunk = size > 512 ? 512 : size; rc = conn_read(buffer1, chunk); if (rc <= 0) csync_fatal("Read-error while receiving data.\n"); chunk = rc; if (sig_file) { if (fread(buffer2, chunk, 1, sig_file) != 1) { csync_debug(2, "Found EOF in local sig file.\n"); found_diff = 1; } if (memcmp(buffer1, buffer2, chunk)) { csync_debug(2, "Found diff in sig at -%d:-%d\n", size, size - chunk); found_diff = 1; } } /* else just drain */ size -= chunk; csync_debug(3, "Got %d bytes, %ld bytes left ..\n", chunk, size); } csync_debug(3, "File has been checked successfully (%s).\n", found_diff ? "difference found" : "files are equal"); if (sig_file) fclose(sig_file); return found_diff; io_error: csync_debug(0, "I/O Error '%s' in rsync-check: %s\n", strerror(errno), prefixsubst(filename)); error: backup_errno = errno; /* drain response */ while (size > 0) { chunk = size > 512 ? 512 : size; rc = conn_read(buffer1, chunk); if (rc <= 0) csync_fatal("Read-error while receiving data.\n"); size -= rc; } if (sig_file) fclose(sig_file); errno = backup_errno; return -1; } void csync_rs_sig(const char *filename) { FILE *sig_file; csync_debug(3, "Csync2 / Librsync: csync_rs_sig('%s')\n", filename); if (csync_rs_sigfile(filename, &sig_file)) { /* error */ if (sig_file) csync_fatal("Got an error from librsync, too bad!\n"); csync_debug(0, "I/O Error '%s' in rsync-sig: %s\n", strerror(errno), prefixsubst(filename)); /* FIXME. * Peer expected some sort of sig, * we need to communicate an error instead. */ conn_printf("octet-stream -1\n"); return; } /* no error */ csync_debug(3, "Sending sig_file to peer..\n"); if (sig_file) { csync_send_file(sig_file); fclose(sig_file); } else { /* This is the signature for an "empty" file * as returned by rs_sig_file(/dev/null). * No point in re-calculating it over and over again. */ conn_printf("octet-stream 12\n"); conn_write("rs\0016\000\000\010\000\000\000\000\010", 12); } csync_debug(3, "Signature has been successfully sent.\n"); } int csync_rs_delta(const char *filename) { FILE *sig_file = 0, *new_file = 0, *delta_file = 0; rs_result result; rs_signature_t *sumset; rs_stats_t stats; char tmpfname[MAXPATHLEN]; csync_debug(3, "Csync2 / Librsync: csync_rs_delta('%s')\n", filename); csync_debug(3, "Receiving sig_file from peer..\n"); sig_file = open_temp_file(tmpfname, prefixsubst(filename)); if ( !sig_file ) goto io_error; if (unlink(tmpfname) < 0) goto io_error; if ( csync_recv_file(sig_file) ) { fclose(sig_file); return -1; } result = rs_loadsig_file(sig_file, &sumset, &stats); if (result != RS_DONE) csync_fatal("Got an error from librsync, too bad!\n"); fclose(sig_file); csync_debug(3, "Opening new_file and delta_file..\n"); new_file = fopen(prefixsubst(filename), "rb"); if ( !new_file ) { int backup_errno = errno; const char *errstr = strerror(errno); csync_debug(0, "I/O Error '%s' while %s in rsync-delta: %s\n", errstr, "opening data file for reading", filename); conn_printf("%s\n", errstr); fclose(new_file); errno = backup_errno; return -1; } delta_file = open_temp_file(tmpfname, prefixsubst(filename)); if ( !delta_file ) goto io_error; if (unlink(tmpfname) < 0) goto io_error; csync_debug(3, "Running rs_build_hash_table() from librsync..\n"); result = rs_build_hash_table(sumset); if (result != RS_DONE) csync_fatal("Got an error from librsync, too bad!\n"); csync_debug(3, "Running rs_delta_file() from librsync..\n"); result = rs_delta_file(sumset, new_file, delta_file, &stats); if (result != RS_DONE) csync_fatal("Got an error from librsync, too bad!\n"); csync_debug(3, "Sending delta_file to peer..\n"); csync_send_file(delta_file); csync_debug(3, "Delta has been created successfully.\n"); rs_free_sumset(sumset); fclose(delta_file); fclose(new_file); return 0; io_error: csync_debug(0, "I/O Error '%s' in rsync-delta: %s\n", strerror(errno), prefixsubst(filename)); if (new_file) fclose(new_file); if (delta_file) fclose(delta_file); if (sig_file) fclose(sig_file); return -1; } int csync_rs_patch(const char *filename) { FILE *basis_file = 0, *delta_file = 0, *new_file = 0; int backup_errno; rs_stats_t stats; rs_result result; char *errstr = "?"; char tmpfname[MAXPATHLEN], newfname[MAXPATHLEN]; csync_debug(3, "Csync2 / Librsync: csync_rs_patch('%s')\n", filename); csync_debug(3, "Receiving delta_file from peer..\n"); delta_file = open_temp_file(tmpfname, prefixsubst(filename)); if ( !delta_file ) { errstr="creating delta temp file"; goto io_error; } if (unlink(tmpfname) < 0) { errstr="removing delta temp file"; goto io_error; } if ( csync_recv_file(delta_file) ) goto error; csync_debug(3, "Opening to be patched file on local host..\n"); basis_file = fopen(prefixsubst(filename), "rb"); if ( !basis_file ) { basis_file = open_temp_file(tmpfname, prefixsubst(filename)); if ( !basis_file ) { errstr="opening data file for reading"; goto io_error; } if (unlink(tmpfname) < 0) { errstr="removing data temp file"; goto io_error; } } csync_debug(3, "Opening temp file for new data on local host..\n"); new_file = open_temp_file(newfname, prefixsubst(filename)); if ( !new_file ) { errstr="creating new data temp file"; goto io_error; } csync_debug(3, "Running rs_patch_file() from librsync..\n"); result = rs_patch_file(basis_file, delta_file, new_file, &stats); if (result != RS_DONE) { csync_debug(0, "Internal error from rsync library!\n"); goto error; } csync_debug(3, "Renaming tmp file to data file..\n"); fclose(basis_file); basis_file = NULL; #ifdef __CYGWIN__ /* TODO: needed? */ // This creates the file using the native windows API, bypassing // the cygwin wrappers and so making sure that we do not mess up the // permissions.. { char winfilename[MAX_PATH]; HANDLE winfh; cygwin_conv_to_win32_path(prefixsubst(filename), winfilename); winfh = CreateFile(TEXT(winfilename), GENERIC_WRITE, // open for writing 0, // do not share NULL, // default security CREATE_ALWAYS, // overwrite existing FILE_ATTRIBUTE_NORMAL | // normal file FILE_FLAG_OVERLAPPED, // asynchronous I/O NULL); // no attr. template if (winfh == INVALID_HANDLE_VALUE) { csync_debug(0, "Win32 I/O Error %d in rsync-patch: %s\n", (int)GetLastError(), winfilename); errno = EACCES; goto error; } CloseHandle(winfh); goto copy; } #endif if (rename(newfname, prefixsubst(filename))) { char buffer[512]; int rc; if (errno != EXDEV) { errstr="renaming tmp file to to be patched file"; goto io_error; } #ifdef __CYGWIN__ copy: #endif csync_debug(1, "rename not possible! Will truncate and copy instead.\n"); basis_file = fopen(prefixsubst(filename), "wb"); if ( !basis_file ) { errstr="opening data file for writing"; goto io_error; } /* FIXME * Can easily lead to partially transfered files on the receiving side! * Think truncate, then connection loss. * Or any other failure scenario. * Need better error checks! */ rewind(new_file); while ( (rc = fread(buffer, 1, 512, new_file)) > 0 && fwrite(buffer, rc, 1, basis_file) == rc ) ; /* at least retain the temp file, if something went wrong. */ if (ferror(new_file) || ferror(basis_file)) { csync_debug(0, "ERROR while copying temp file '%s' to basis file '%s'; " "basis file may be corrupted; temp file has been retained.\n", newfname, prefixsubst(filename)); goto error; } unlink(newfname); fclose(basis_file); basis_file = NULL; } csync_debug(3, "File has been patched successfully.\n"); fclose(delta_file); fclose(new_file); return 0; io_error: backup_errno = errno; csync_debug(0, "I/O Error '%s' while %s in rsync-patch: %s\n", strerror(errno), errstr, prefixsubst(filename)); errno = backup_errno; error:; backup_errno = errno; if ( delta_file ) fclose(delta_file); if ( basis_file ) fclose(basis_file); if ( new_file ) fclose(new_file); errno = backup_errno; return -1; } csync2-2.0-22-gce67c55/tests/000077500000000000000000000000001333746731400154005ustar00rootroot00000000000000csync2-2.0-22-gce67c55/tests/etc/000077500000000000000000000000001333746731400161535ustar00rootroot00000000000000csync2-2.0-22-gce67c55/tests/etc/csync2.key_demo000066400000000000000000000001011333746731400210620ustar00rootroot00000000000000DOajyiQWtEFQROdyMViFP5IMGbVQaU5MhFWLfWe1955PbMLm53hg2jEKVwkaL5qv csync2-2.0-22-gce67c55/tests/include.sh000066400000000000000000000210511333746731400173560ustar00rootroot00000000000000#!/bin/bash ################################################################ # This is a small collection of helper functions to produce # TAP, as in http://en.wikipedia.org/wiki/Test_Anything_Protocol # excercised by prove(1) # # # Minimal Introduction # -------------------- # # Each test script (*/*.t) will include this file, # which will generate a "testing plan # http://search.cpan.org/~petdance/Test-Harness-2.64/lib/Test/Harness/TAP.pod#THE_TAP_FORMAT # # TAP's general format is: # # 1..N # ok 1 Description # Directive # # Diagnostic # .... # not ok 46 Description # ok 47 Description # ok 48 Description # not ok 49 # TODO not yet implemented # not ok 50 # SKIP needs 77bit architecture # more tests.... # # Skipping whole test files: # # 1..0 # Skipped: WWW::Mechanize not installed # # Bail out! # # As an emergency measure a test script can decide that further tests are useless # (e.g. missing dependencies) and testing should stop immediately. In that case # the test script prints the magic words # # Bail out! MySQL is not running. # ################################################################ # You better not use fd 44 for anything else... exec 44>&1 bail_out() { >&44 echo "Bail out! $*" # no exit trap on bailout. trap - EXIT exit 1 } dbg() { local lvl=$1 shift (( $DEBUG_LEVEL < $lvl )) && return >&44 echo "##<$lvl># $*" } # Sometimes (e.g. when doing "-TT"), single shot ("-iii") is not good enough. # But we should be able to kill that daemon without using "killall". # So several layers of indirection are bad, # the caller would not know whom to kill really. csync2_daemon() { dbg 1 "CSYNC2_SYSTEM_DIR=$CSYNC2_SYSTEM_DIR csync2 -D $CSYNC2_DATABASE $*" exec "$SOURCE_DIR/csync2" -D "$CSYNC2_DATABASE" "$@" } csync2() { local ex dbg 1 "CSYNC2_SYSTEM_DIR=$CSYNC2_SYSTEM_DIR csync2 -D $CSYNC2_DATABASE $*" command "$SOURCE_DIR/csync2" -D "$CSYNC2_DATABASE" "$@" ex=$? dbg 1 "exit code: $ex" return $ex } ## Does NOT remove the config file cleanup() { local host local i case $CSYNC2_DATABASE in sqlite://*|sqlite2://*|sqlite3://*|/*) mkdir -p "$CSYNC2_DATABASE/" rm -f "$CSYNC2_DATABASE/"*.csync2.test.db* ;; pgsql://csync2:csync2@*/) host=${CSYNC2_DATABASE#pgsql://csync2:csync2@} host=${host%/} [[ $host = *[/\ ]* ]] && bail_out "cannot use $CSYNC2_DATABASE" for i in {1..9}; do psql "host=$host user=csync2 password=csync2 dbname=postgres" \ -c "DROP DATABASE IF EXISTS csync2_${i}_csync2_test;" \ || bail_out "could not cleanup postgres database $i" done ;; mysql://csync2:csync2@*/) host=${CSYNC2_DATABASE#mysql://csync2:csync2@} host=${host%/} [[ $host = *[/\ ]* ]] && bail_out "cannot use $CSYNC2_DATABASE" for i in {1..9}; do mysql --host=$host --user=csync2 --password=csync2 mysql \ -e "DROP DATABASE IF EXISTS csync2_${i}_csync2_test;" \ || bail_out "could not cleanup mysql database $i" done ;; *) bail_out "unsupported CSYNC2_DATABASE setting $CSYNC2_DATABASE" ;; esac rm -rf "$TESTS_DIR/"{1..9} mkdir -p "$TESTS_DIR/"{1..9} } prepare_etc_hosts_bring_up_ips() { for i in {1..9}; do eval N$i=$i.csync2.test eval IP$i=127.2.1.$i eval D$i=$TESTS_DIR/$i grep -qFxe "127.2.1.$i $i.csync2.test" /etc/hosts \ || echo "127.2.1.$i $i.csync2.test" >> /etc/hosts ip -o -f inet a s dev lo | grep -qFe " 127.2.1.$i/" || ip a add dev lo 127.2.1.$i/24 done } # generates a new config file with proper %demodir% prefixes prepare_cfg_file() { local CFG="$CSYNC2_SYSTEM_DIR/csync2.cfg"; if test -e "$CFG" ; then dbg 1 "$CFG already in place, using it as is" return fi dbg 0 "generating $CFG" cat > "$CFG" <<-___ group demo { host 1.csync2.test; host 2.csync2.test; key csync2.key_demo; include %demodir%; exclude %demodir%/e; } prefix demodir { on 1.csync2.test: $TESTS_DIR/1; on 2.csync2.test: $TESTS_DIR/2; on 3.csync2.test: $TESTS_DIR/3; on 4.csync2.test: $TESTS_DIR/4; on 5.csync2.test: $TESTS_DIR/5; on 6.csync2.test: $TESTS_DIR/6; on 7.csync2.test: $TESTS_DIR/7; on 8.csync2.test: $TESTS_DIR/8; on 9.csync2.test: $TESTS_DIR/9; } nossl * *; ___ } __test_result_header() { printf "# %s %s %s\n" $TEST_CALL_STACK echo "# =========== $description = trace = {{{3" sed -e 's/^/# -x : /' < "$TESTS_TMP_DIR/xtrace" echo "# =========== $description = err = {{{3" sed -e 's/^/# err: /' < "$TESTS_TMP_DIR/err" echo "# =========== $description = out = {{{2" sed -e 's/^/# out: /' < "$TESTS_TMP_DIR/out" echo "# =========== $description = exit:$result = expected:$expected_result_code }}}1" } __test_not_ok() { __test_result_header echo "not ok $__test_cur - $description" let __test_not_ok_cnt++ return 1 } __test_ok() { (( $DEBUG_LEVEL > 1 )) && __test_result_header echo "ok $__test_cur - $description" let __test_ok_cnt++ return 0 } _TEST() { let __test_cur++ local expected_result_code=$1; shift local description=$1; shift local result="N/A" export TEST_DESCRIPTION="$0 $description" export TEST_CALL_STACK="$(i=0; while caller $i; do let i++; done)" # truncate stdout/stderr/xtrace capture files : >"$TESTS_TMP_DIR/out" >"$TESTS_TMP_DIR/err" >"$TESTS_TMP_DIR/xtrace" echo "# =========== "$test_line" {{{1" if [[ $# = 0 ]] ; then __test_not_ok "Bad test line" bail_out "You need to fix the test scripts first" return 1 fi ( set -e cd "$TESTS_TMP_DIR" exec >>out 2>>err 45>>xtrace BASH_XTRACEFD=45 : $BASHPID set +e -x "$@" ) result=$? if [[ ${GIVE_ME_A_SHELL_AFTER_EACH_TEST-} ]] && test -t 0 && test -t 1 ; then ( cd "$TESTS_TMP_DIR" echo "-----------" echo "CSYNC2 TEST debug shell ..." echo "$0 -- "$test_line echo "To abort further tests: touch bailout" echo " -------------" export -f csync2 dbg bash ) test -e "$TESTS_TMP_DIR/bailout" && bail_out "$TESTS_TMP_DIR/bailout" fi if [[ $result = $expected_result_code ]] ; then __test_ok else __test_not_ok [[ ${BAIL_OUT_EARLY:-} ]] && bail_out "$BAIL_OUT_EARLY" fi } TEST() { local test_line="TEST '$1' ${*:2}" _TEST 0 "$@" } TEST_EXPECT_EXIT_CODE() { local test_line="TEST_EXPECT_EXIT_CODE [expected:$1] '$2' ${*:3}" _TEST "$@" } TEST_BREAK() { GIVE_ME_A_SHELL_AFTER_EACH_TEST=1 TEST "$@" } require() { if ! "$@"; then echo "# $*" echo "1..0 # Skipped: test requirements failed" exit 1 fi } ############################################################################# ## some helper functions that are likely to be reused by many test scripts ## ############################################################################# csync2_u() { local tmp kid now client_exit nc_exit server_exit if csync2 -N $1 -M > /dev/null; then csync2 -N $2 -iii -vvv & kid=$! # try to avoid the connect-before-listen race, # wait for the expected listening socket to show up, # with timeout using bash magic $SECONDS. now=$SECONDS while ! ss -tnl src $2:csync2 | grep -q ^LISTEN; do kill -0 $kid (( SECONDS - now < 2 )) sleep 0.1 done csync2 -N $1 -uvvv client_exit=$? if ss -tnl src $2:csync2 | grep -q ^LISTEN; then # server still alive? # then no connection was made... # attempt to do one now. tmp=$(nc $2 $CSYNC2_PORT <<<"BYE" ) nc_exit=$? [[ $nc_exit = 0 ]] || kill $kid else nc_exit=1 fi wait $kid server_exit=$? [[ $client_exit = 0 && $server_exit = 0 && $nc_exit != 0 ]] else echo "Apparently nothing dirty on $1, not starting server on $2" # but still do the csync2 -u ... csync2 -N $1 -uvvv fi } ################################ ## Basic setup code follows ## ################################ set -u if [[ $BASH_SOURCE == include.sh ]] ; then TESTS_DIR=$PWD elif [[ $BASH_SOURCE == */include.sh ]]; then TESTS_DIR=${BASH_SOURCE%/include.sh} else bail_out "Sorry, I'm confused..." fi TESTS_DIR=$(cd "$TESTS_DIR" && pwd ) SOURCE_DIR=$(cd "$TESTS_DIR/.." && pwd ) : CSYNC2_PORT ${CSYNC2_PORT:=30865} : CSYNC2_SYSTEM_DIR ${CSYNC2_SYSTEM_DIR:=$TESTS_DIR/etc} : CSYNC2_DATABASE ${CSYNC2_DATABASE:=$TESTS_DIR/db} : DEBUG_LEVEL=${DEBUG_LEVEL:=0} __test_cur=0 __test_ok_cnt=0 __test_not_ok_cnt=0 case "$0" in -bash|bash) echo >&2 "Interactive mode??" __test_total_cnt=0 export -f csync2 ;; *) TESTS_TMP_DIR=$(mktemp -d "$TESTS_DIR/tmp.XXX") trap 'rm -rf "$TESTS_TMP_DIR"' EXIT __test_total_cnt=$(grep -Ece '^[[:space:]]*\<(TEST|TEST_EXPECT_EXIT_CODE|TEST_BREAK)\>' $0) ;; esac if [[ $# -gt 1 ]] && [[ $1 = require ]]; then "$@" fi export CSYNC2_SYSTEM_DIR CSYNC2_DATABASE export TESTS_DIR TESTS_TMP_DIR SOURCE_DIR prepare_etc_hosts_bring_up_ips prepare_cfg_file # The Plan echo 1..$__test_total_cnt csync2-2.0-22-gce67c55/tests/t/000077500000000000000000000000001333746731400156435ustar00rootroot00000000000000csync2-2.0-22-gce67c55/tests/t/0000.basic.t000066400000000000000000000077171333746731400175030ustar00rootroot00000000000000#!/bin/bash . $(dirname $0)/../include.sh # That prepared some "node names" ($N1 .. $N9) and corresponding # IPv4 addresses in /etc/hosts ($IP1 .. $IP9, 127.2.1.1 -- *.9) # as well as variables $D1 .. $D9 to point to the respective sub tree # of those "test node" instances. # Cleanup does clean the data base and the test directories. # You probably want to do a cleanup first thing in each test script. # You may even do it several times throughout such a script. cleanup # You may or may not want to re-prepare the config file every time. # Feel free to reuse in later test scripts what is generated here: #prepare_cfg_file || bail_out "Unable to create config file" # Which sets up a prefix %demodir% for each of the "instances", # and has these directives: # include %demodir%/; # exclude %demodir%/exclude; # to be able to do some basic checks. # A "Test" needs # * an expectation (exit code) # * a description, # * a "simple command" with optional arguments. # TEST is shorthand for TEST_EXPECT_EXIT_CODE 0 # ALL tests in this just have to work. # bail out at the first failure BAIL_OUT_EARLY=1 TEST_EXPECT_EXIT_CODE 1 "usage output" csync2 TEST_EXPECT_EXIT_CODE 1 "host not mentioned" csync2 -L -N does.not.exist TEST_EXPECT_EXIT_CODE 2 "list non-existent db" csync2 -L -N $N1 # You are free to do whatever you want # in preparation for the next test, # remove some files, create some files, change some content # However, DO NOT use redirection or non-simple commands directly on a "TEST" # command line. If you need that, use eval, like so: # TEST "short description" eval 'some | involved | pipe || other > with 2> redirection' # populate $D1 # ------------ mkdir -p $D1/a touch $D1/a/f # The simple command given to TEST is executed in a temp directory, # and its stdout and stderr are redirected to files in there. # So don't rely on the location of "." # Note: intentionally not using -I here, # I want everything to be flagges as dirty. TEST "init db 1" csync2 -N $N1 -crvv $D1 TEST "list db 1" csync2 -N $N1 -L # populate $D2 # ------------ mkdir -p $D2/b TEST "touch it 2" touch -d "last week" $D2/b/f TEST "init db 2" csync2 -N $N2 -cIrvv $D2 # If you need more than a "simple" command, # define a shell function, and test that. # expect this number of records in the "known" database now: t() { [[ $(csync2 -N $N2 -L | wc -l) = 3 ]] ; } TEST "list db 2" t # But since it was an "init" run (-I), # nothing should be dirty. TEST_EXPECT_EXIT_CODE 2 "nothing dirty" csync2 -N $N2 -M # This time, we expect it to populate the dirty table, # and -M should have an exit code of 0. TEST "touch again 2" eval "date > $D2/b/f" TEST "check db 2" csync2 -N $N2 -crvv $D2 TEST "list dirty 2" csync2 -N $N2 -M # compare and sync between both instances # --------------------------------------- # Verify the current diff: csync2_T() { csync2 -N $1 -iii & tmp=$( csync2 -N $2 -T ) [[ "$tmp" = "$3" ]] } TEST "csync2 -T" csync2_T $N1 $N2 "\ R 2.csync2.test 1.csync2.test %demodir%/a R 2.csync2.test 1.csync2.test %demodir%/a/f L 2.csync2.test 1.csync2.test %demodir%/b L 2.csync2.test 1.csync2.test %demodir%/b/f" # sync up TEST "csync2 -uv" csync2_u $N1 $N2 TEST "csync2 -uv" csync2_u $N2 $N1 # and now, they should be in sync TEST "csync2 -T" csync2_T $N1 $N2 "" TEST "diff -rq" diff -rq $D1 $D2 # and checking again should not redirty anything. TEST "check 1" csync2 -N $N1 -crvv $D1 TEST "check 2" csync2 -N $N2 -crvv $D2 TEST_EXPECT_EXIT_CODE 2 "nothing dirty" csync2 -N $N1 -M TEST_EXPECT_EXIT_CODE 2 "nothing dirty" csync2 -N $N2 -M TEST "csync2 -T" csync2_T $N1 $N2 "" # remove some stuff rm -rf $D1/* TEST "check post rm" csync2 -N $N1 -crv $D1 TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 ############ # This was the absolutely basic functionality test. # If any of the tests in this file failed, # do not even attempt further testing. # # Keep this last. [[ $__test_ok_cnt = $__test_total_cnt ]] || bail_out "Some basic sanity checks failed, no use testing anything else." csync2-2.0-22-gce67c55/tests/t/0001.missing-leading-directories.t000066400000000000000000000031301333746731400237700ustar00rootroot00000000000000#!/bin/bash . $(dirname $0)/../include.sh # That prepared some "node names" ($N1 .. $N9) and corresponding # IPv4 addresses in /etc/hosts ($IP1 .. $IP9, 127.2.1.1 -- *.9) # as well as variables $D1 .. $D9 to point to the respective sub tree # of those "test node" instances. # Cleanup does clean the data base and the test directories. # You probably want to do a cleanup first thing in each test script. # You may even do it several times throughout such a script. cleanup # A "Test" needs # * an expectation (exit code) # * a description, # * a "simple command" with optional arguments. # TEST is shorthand for TEST_EXPECT_EXIT_CODE 0 TEST_EXPECT_EXIT_CODE 2 "list non-existent db" csync2 -L -N $N1 TEST_EXPECT_EXIT_CODE 2 "list non-existent db" csync2 -L -N $N2 # You are free to do whatever you want # in preparation for the next test, # remove some files, create some files, change some content # populate $D1 # ------------ mkdir -p $D1/{a,b,c}/d/e TEST "init db 1" csync2 -N $N1 -cIr $D1 touch -d "last week" $D1/{a,b,c}/d/e/f TEST "check" csync2 -N $N1 -cr $D1 TEST "list dirty" csync2 -N $N1 -M # compare and sync between both instances # --------------------------------------- TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 # create conflicts # ---------------- rm -rf $D2/{a,b,c} touch $D1/{a,b,c}/d/e/f TEST "check again" csync2 -N $N1 -cr $D1 TEST "list dirty" csync2 -N $N1 -M TEST_EXPECT_EXIT_CODE 1 "csync2 -uv" csync2_u $N1 $N2 # force 1 -> 2 # ---------------- TEST "force" csync2 -N $N1 -frv $D1 TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 csync2-2.0-22-gce67c55/tests/t/0002.long-and-strange-file-names.t000066400000000000000000000023771333746731400235770ustar00rootroot00000000000000#!/bin/bash # cd to canonical path of . cd -P . . $(dirname $0)/../include.sh # That prepared some "node names" ($N1 .. $N9) and corresponding # IPv4 addresses in /etc/hosts ($IP1 .. $IP9, 127.2.1.1 -- *.9) # as well as variables $D1 .. $D9 to point to the respective sub tree # of those "test node" instances. # Cleanup does clean the data base and the test directories. # You probably want to do a cleanup first thing in each test script. # You may even do it several times throughout such a script. cleanup # populate $D1 # ------------ f_trailing_space_pad_to_255=$(printf "%-255s" 'f trailing space pad to 255, contains umlauts äüöÄÖÜß and other special chars ☺ smiley : colon ^H ^? \\backslash single '\'' and double " quote') long_dir=$(perl -e 'print((("D" x 255) . "/") x 16)') create_tree() { ( set -xe date > "$D1/$f_trailing_space_pad_to_255" d=$D1/$long_dir d=${d::4095} d=${d%/} mkdir -p "$d" d=${d:: (4095 - 256)} d=${d%/} mkdir "$d" date > "$d/$f_trailing_space_pad_to_255" ) } TEST "create tree" create_tree TEST "check" csync2 -N $N1 -cr $D1 TEST "list dirty" csync2 -N $N1 -M # compare and sync between both instances # --------------------------------------- TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 csync2-2.0-22-gce67c55/tests/t/0003.case-compare.t000066400000000000000000000020401333746731400207440ustar00rootroot00000000000000#!/bin/bash . $(dirname $0)/../include.sh # That prepared some "node names" ($N1 .. $N9) and corresponding # IPv4 addresses in /etc/hosts ($IP1 .. $IP9, 127.2.1.1 -- *.9) # as well as variables $D1 .. $D9 to point to the respective sub tree # of those "test node" instances. # Cleanup does clean the data base and the test directories. # You probably want to do a cleanup first thing in each test script. # You may even do it several times throughout such a script. cleanup # populate $D1 # ------------ create_some_files() { date | tee $D1/1.txt $D1/hello.txt date | tee $D1/1.TXT $D1/Hello.txt } TEST "create some files" create_some_files TEST "check" csync2 -N $N1 -cr $D1 TEST "list dirty" csync2 -N $N1 -M # compare and sync between both instances # --------------------------------------- TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 # redirty only one date | tee -a $D1/1.txt $D1/hello.txt TEST "check" csync2 -N $N1 -cr $D1 t() { [[ $(csync2 -N $N1 -M | wc -l) = 2 ]] ; } TEST "only lower case should be dirty" t csync2-2.0-22-gce67c55/tests/t/0004.diff.t000066400000000000000000000042321333746731400173230ustar00rootroot00000000000000#!/bin/bash . $(dirname $0)/../include.sh # That prepared some "node names" ($N1 .. $N9) and corresponding # IPv4 addresses in /etc/hosts ($IP1 .. $IP9, 127.2.1.1 -- *.9) # as well as variables $D1 .. $D9 to point to the respective sub tree # of those "test node" instances. # Cleanup does clean the data base and the test directories. # You probably want to do a cleanup first thing in each test script. # You may even do it several times throughout such a script. cleanup # A "Test" needs # * an expectation (exit code) # * a description, # * a "simple command" with optional arguments. # TEST is shorthand for TEST_EXPECT_EXIT_CODE 0 TEST_EXPECT_EXIT_CODE 2 "list non-existent db" csync2 -L -N $N1 TEST_EXPECT_EXIT_CODE 2 "list non-existent db" csync2 -L -N $N2 # You are free to do whatever you want # in preparation for the next test, # remove some files, create some files, change some content # populate $D1 # ------------ mkdir -p $D1/a TEST "init db 1" csync2 -N $N1 -cIr $D1 seq 1000 | tee $D1/a/f >/dev/null touch -d "last week" $D1/a/f TEST "check" csync2 -N $N1 -cr $D1 TEST "list dirty" csync2 -N $N1 -M # compare and sync between both instances # --------------------------------------- TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 # create conflicts # ---------------- seq 1000 | tee $D1/a/f >/dev/null seq 999 | tee $D2/a/f >/dev/null TEST "check again" csync2 -N $N1 -cr $D1 TEST "list dirty" csync2 -N $N1 -M # Verify the current diff: csync2_T() { csync2 -N $1 -iii & sleep 1 tmp=$( csync2 -N $2 -T ) [[ "$tmp" = "$3" ]] } csync2_TT() { csync2_daemon -N $1 -ii & d_pid=$! sleep 1 tmp=$( csync2 -N $2 -TT $2 $1 $3 ) kill $d_pid && wait [[ "$tmp" = "$4" ]] } TEST "csync2 -T" csync2_T $N1 $N2 "\ R 2.csync2.test 1.csync2.test %demodir% X 2.csync2.test 1.csync2.test %demodir%/a/f" TEST "csync2 -TT" csync2_TT $N1 $N2 $D2/a/f "\ --- 1.csync2.test:%demodir%/a/f +++ 2.csync2.test:%demodir%/a/f @@ -997,4 +997,3 @@ 997 998 999 -1000" TEST_EXPECT_EXIT_CODE 1 "csync2 -uv" csync2_u $N1 $N2 # force 1 -> 2 # ---------------- TEST "force" csync2 -N $N1 -frv $D1 TEST "csync2 -uv" csync2_u $N1 $N2 TEST "diff -rq" diff -rq $D1 $D2 csync2-2.0-22-gce67c55/update.c000066400000000000000000001013251333746731400156660ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #include #include #include #include #include #include #include #include static int connection_closed_error = 1; enum connection_response read_conn_status(const char *file, const char *host) { char line[4096]; enum connection_response conn_status; if ( conn_gets(line, sizeof(line)) ) { conn_status = conn_response_to_enum(line); if (!is_ok_response(conn_status)) csync_error_count++; } else { csync_error_count++; connection_closed_error = 1; sprintf(line, "%s\n", conn_response(CR_ERR_CONN_CLOSED)); conn_status = CR_ERR_CONN_CLOSED; } if ( file ) csync_debug(2, "While syncing file %s:\n", file); else file = ""; csync_debug(3, "response from peer(%s): %s [%u] <- %s", file, host, conn_status, line); return conn_status; } int connect_to_host(const char *peername) { int use_ssl = 1; struct csync_nossl *t; connection_closed_error = 0; for (t = csync_nossl; t; t=t->next) { if ( !fnmatch(t->pattern_from, myhostname, 0) && !fnmatch(t->pattern_to, peername, 0) ) { use_ssl = 0; break; } } csync_debug(1, "Connecting to host %s (%s) ...\n", peername, use_ssl ? "SSL" : "PLAIN"); if ( conn_open(peername) ) return -1; if ( use_ssl ) { #if HAVE_LIBGNUTLS conn_printf("SSL\n"); if ( read_conn_status(0, peername) != CR_OK_ACTIVATING_SSL ) { csync_debug(1, "SSL command failed.\n"); conn_close(); return -1; } conn_activate_ssl(0); conn_check_peer_cert(peername, 1); #else csync_debug(0, "ERROR: Config request SSL but this csync2 is built without SSL support.\n"); csync_error_count++; return -1; #endif } conn_printf("CONFIG %s\n", url_encode(cfgname)); if (!is_ok_response(read_conn_status(NULL, peername))) { csync_debug(1, "Config command failed.\n"); conn_close(); return -1; } if (active_grouplist) { conn_printf("GROUP %s\n", url_encode(active_grouplist)); if (!is_ok_response(read_conn_status(NULL, peername))) { csync_debug(1, "Group command failed.\n"); conn_close(); return -1; } } return 0; } static int get_auto_method(const char *peername, const char *filename) { const struct csync_group *g = 0; const struct csync_group_host *h; while ( (g=csync_find_next(g, filename)) ) { for (h = g->host; h; h = h->next) { if (!strcmp(h->hostname, peername)) { if (g->auto_method == CSYNC_AUTO_METHOD_LEFT && h->on_left_side) return CSYNC_AUTO_METHOD_LEFT_RIGHT_LOST; if (g->auto_method == CSYNC_AUTO_METHOD_RIGHT && !h->on_left_side) return CSYNC_AUTO_METHOD_LEFT_RIGHT_LOST; return g->auto_method; } } } return CSYNC_AUTO_METHOD_NONE; } static int get_master_slave_status(const char *peername, const char *filename) { const struct csync_group *g = 0; const struct csync_group_host *h; while ( (g=csync_find_next(g, filename)) ) { if (g->local_slave) continue; for (h = g->host; h; h = h->next) { if (h->slave && !strcmp(h->hostname, peername)) return 1; } } return 0; } void csync_update_file_del(const char *peername, const char *filename, int force, int dry_run) { enum connection_response last_conn_status; int auto_resolve_run = 0; const char *key = csync_key(peername, filename); if ( !key ) { csync_debug(2, "Skipping deletion %s on %s - not in my groups.\n", filename, peername); return; } auto_resolve_entry_point: csync_debug(1, "Deleting %s on %s ...\n", filename, peername); if ( force ) { if ( dry_run ) { printf("!D: %-15s %s\n", peername, filename); return; } conn_printf("FLUSH %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; } else { static char chk1[4 * 4096]; const char *chk2 = "---"; int i, found_diff = 0; int rs_check_result; conn_printf("SIG %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (last_conn_status == CR_ERR_PARENT_DIR_MISSING) { /* In this case, this is not really an error. */ --csync_error_count; goto already_gone; } if (!is_ok_response(last_conn_status)) goto got_error; if ( !conn_gets(chk1, sizeof(chk1)) ) goto got_error; for (i=0; chk1[i] && chk1[i] != '\n' && chk2[i]; i++) if ( chk1[i] != chk2[i] ) { csync_debug(2, "File is different on peer (cktxt char #%d).\n", i); csync_debug(2, ">>> PEER: %s>>> LOCAL: %s\n", chk1, chk2); found_diff=1; break; } rs_check_result = csync_rs_check(filename, 0); if ( rs_check_result < 0 ) goto got_error; if ( rs_check_result ) { csync_debug(2, "File is different on peer (rsync sig).\n"); found_diff=1; } if (!is_ok_response(read_conn_status(filename, peername))) goto got_error; if ( !found_diff ) { already_gone: csync_debug(1, "File is already up to date on peer.\n"); if ( dry_run ) { printf("?S: %-15s %s\n", peername, filename); // DS Remove local dirty, even in dry run // return; } goto skip_action; } if ( dry_run ) { printf("?D: %-15s %s\n", peername, filename); return; } } conn_printf("DEL %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); /* FIXME be more specific? * (last_conn_status == CR_ERR_ALSO_DIRTY_HERE) ?? */ if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; skip_action: SQL("Remove dirty-file entry.", "DELETE FROM dirty WHERE filename = '%s' " "AND peername = '%s'", url_encode(filename), url_encode(peername)); if (auto_resolve_run) csync_error_count--; return; maybe_auto_resolve: if (!auto_resolve_run && (last_conn_status == CR_ERR_ALSO_DIRTY_HERE ) ) { csync_debug(0,"auto resolving... response = %d",last_conn_status); if (get_master_slave_status(peername, filename)) { csync_debug(0, "Auto-resolving conflict: Won 'master/slave' test.\n"); auto_resolve_run = 1; } else { int auto_method = get_auto_method(peername, filename); switch (auto_method) { case CSYNC_AUTO_METHOD_FIRST: auto_resolve_run = 1; csync_debug(0, "Auto-resolving conflict: Won 'first' test.\n"); break; case CSYNC_AUTO_METHOD_LEFT: case CSYNC_AUTO_METHOD_RIGHT: auto_resolve_run = 1; csync_debug(0, "Auto-resolving conflict: Won 'left/right' test.\n"); break; case CSYNC_AUTO_METHOD_LEFT_RIGHT_LOST: csync_debug(0, "Do not auto-resolve conflict: Lost 'left/right' test.\n"); break; case CSYNC_AUTO_METHOD_YOUNGER: case CSYNC_AUTO_METHOD_OLDER: case CSYNC_AUTO_METHOD_BIGGER: case CSYNC_AUTO_METHOD_SMALLER: csync_debug(0, "Do not auto-resolve conflict: This is a removal.\n"); break; } } if (auto_resolve_run) { force = 1; goto auto_resolve_entry_point; } } got_error: if (auto_resolve_run) csync_debug(0, "ERROR: Auto-resolving failed. Giving up.\n"); csync_debug(1, "File stays in dirty state. Try again later...\n"); } enum connection_response csync_update_file_mod(const char *peername, const char *filename, int force, int dry_run) { struct stat st; enum connection_response last_conn_status = CR_ERROR; int auto_resolve_run = 0; const char *key = csync_key(peername, filename); if ( !key ) { csync_debug(2, "Skipping file update %s on %s - not in my groups.\n", filename, peername); return CR_ERROR; } auto_resolve_entry_point: csync_debug(1, "Updating %s on %s ...\n", filename, peername); if ( lstat_strict(prefixsubst(filename), &st) != 0 ) { csync_debug(0, "ERROR: Cant stat %s.\n", filename); csync_error_count++; goto got_error; } if ( force ) { if ( dry_run ) { printf("!M: %-15s %s\n", peername, filename); return CR_OK; } conn_printf("FLUSH %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; } else { static char chk1[4 * 4096]; const char *chk2; int i, found_diff = 0; int rs_check_result; conn_printf("SIG %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) { csync_debug(3, "error from peer\n"); goto got_error; } if ( !conn_gets(chk1, sizeof(chk1)) ) goto got_error; chk2 = csync_genchecktxt(&st, filename, 1); for (i=0; chk1[i] && chk1[i] != '\n' && chk2[i]; i++) if ( chk1[i] != chk2[i] ) { csync_debug(2, "File %s is different on peer (cktxt char #%d).\n",filename, i); csync_debug(2, ">>> PEER: %s>>> LOCAL: %s\n", chk1, chk2); found_diff=1; break; } rs_check_result = csync_rs_check(filename, S_ISREG(st.st_mode)); if ( rs_check_result < 0 ) goto got_error; if ( rs_check_result ) { csync_debug(2, "File is different on peer (rsync sig).\n"); found_diff=1; } last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; if ( !found_diff ) { csync_debug(1, "File is already up to date on peer.\n"); if ( dry_run ) { printf("?S: %-15s %s\n", peername, filename); // DS also skip on dry_run // return; } goto skip_action; } if ( dry_run ) { printf("?M: %-15s %s\n", peername, filename); return last_conn_status; } } if ( S_ISREG(st.st_mode) ) { conn_printf("PATCH %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); /* FIXME be more specific? * (last_conn_status != CR_OK_SEND_DATA) ?? * (last_conn_status == CR_ERR_ALSO_DIRTY_HERE) ?? */ if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; if ( csync_rs_delta(filename) ) { //why is the response ignored? last_conn_status = read_conn_status(filename, peername); goto got_error; } last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; } else if ( S_ISDIR(st.st_mode) ) { conn_printf("MKDIR %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; } else if ( S_ISCHR(st.st_mode) ) { conn_printf("MKCHR %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; } else if ( S_ISBLK(st.st_mode) ) { conn_printf("MKBLK %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; } else if ( S_ISFIFO(st.st_mode) ) { conn_printf("MKFIFO %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; } else if ( S_ISLNK(st.st_mode) ) { char target[1024]; int rc; rc = readlink(prefixsubst(filename), target, 1023); if ( rc >= 0 ) { target[rc]=0; conn_printf("MKLINK %s %s %s\n", url_encode(key), url_encode(filename), url_encode(target)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; } else { csync_debug(1, "File is a symlink but radlink() failed.\n", st.st_mode); goto got_error; } } else if ( S_ISSOCK(st.st_mode) ) { conn_printf("MKSOCK %s %s\n", url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto maybe_auto_resolve; } else { csync_debug(1, "File type (mode=%o) is not supported.\n", st.st_mode); goto got_error; } conn_printf("SETOWN %s %s %d %d\n", url_encode(key), url_encode(filename), st.st_uid, st.st_gid); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; if ( !S_ISLNK(st.st_mode) ) { conn_printf("SETMOD %s %s %d\n", url_encode(key), url_encode(filename), st.st_mode); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; } skip_action: if ( !S_ISLNK(st.st_mode) ) { conn_printf("SETIME %s %s %Ld\n", url_encode(key), url_encode(filename), (long long)st.st_mtime); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; } SQL("Remove dirty-file entry.", "DELETE FROM dirty WHERE filename = '%s' " "AND peername = '%s'", url_encode(filename), url_encode(peername)); if (auto_resolve_run) csync_error_count--; return last_conn_status; maybe_auto_resolve: if (!auto_resolve_run && (last_conn_status == CR_ERR_ALSO_DIRTY_HERE)) { if (get_master_slave_status(peername, filename)) { csync_debug(0, "Auto-resolving conflict: Won 'master/slave' test.\n"); auto_resolve_run = 1; } else { int auto_method = get_auto_method(peername, filename); switch (auto_method) { case CSYNC_AUTO_METHOD_FIRST: auto_resolve_run = 1; csync_debug(0, "Auto-resolving conflict: Won 'first' test.\n"); break; case CSYNC_AUTO_METHOD_LEFT: case CSYNC_AUTO_METHOD_RIGHT: auto_resolve_run = 1; csync_debug(0, "Auto-resolving conflict: Won 'left/right' test.\n"); break; case CSYNC_AUTO_METHOD_LEFT_RIGHT_LOST: csync_debug(0, "Do not auto-resolve conflict: Lost 'left/right' test.\n"); break; case CSYNC_AUTO_METHOD_YOUNGER: case CSYNC_AUTO_METHOD_OLDER: case CSYNC_AUTO_METHOD_BIGGER: case CSYNC_AUTO_METHOD_SMALLER: { static char buffer[4 * 4096]; char *type, *cmd; long remotedata, localdata; struct stat sbuf; if (auto_method == CSYNC_AUTO_METHOD_YOUNGER || auto_method == CSYNC_AUTO_METHOD_OLDER) { type = "younger/older"; cmd = "GETTM"; } else { type = "bigger/smaller"; cmd = "GETSZ"; } conn_printf("%s %s %s\n", cmd, url_encode(key), url_encode(filename)); last_conn_status = read_conn_status(filename, peername); if (!is_ok_response(last_conn_status)) goto got_error; if ( !conn_gets(buffer, sizeof(buffer)) ) goto got_error_in_autoresolve; remotedata = atol(buffer); if (remotedata == -1) goto remote_file_has_been_removed; if ( lstat_strict(prefixsubst(filename), &sbuf) ) goto got_error_in_autoresolve; if (auto_method == CSYNC_AUTO_METHOD_YOUNGER || auto_method == CSYNC_AUTO_METHOD_OLDER) localdata = sbuf.st_mtime; else localdata = sbuf.st_size; if ((localdata > remotedata) == (auto_method == CSYNC_AUTO_METHOD_YOUNGER || auto_method == CSYNC_AUTO_METHOD_BIGGER)) { remote_file_has_been_removed: auto_resolve_run = 1; csync_debug(0, "Auto-resolving conflict: Won '%s' test.\n", type); } else csync_debug(0, "Do not auto-resolve conflict: Lost '%s' test.\n", type); break; } } } if (auto_resolve_run) { force = 1; goto auto_resolve_entry_point; } } got_error: if (auto_resolve_run) got_error_in_autoresolve: csync_debug(0, "ERROR: Auto-resolving failed. Giving up.\n"); csync_debug(1, "File stays in dirty state. Try again later...\n"); return last_conn_status; } int compare_files(const char *filename, const char *pattern, int recursive) { int i; if (!strcmp(pattern, "/")) return 1; for (i=0; filename[i] && pattern[i]; i++) if (filename[i] != pattern[i]) return 0; if ( filename[i] == '/' && !pattern[i] && recursive) return 1; if ( !filename[i] && !pattern[i]) return 1; return 0; } struct textlist *csync_find_files_recursive(const char *filename) { const char *ue_filename = url_encode(filename); struct textlist *tl = NULL; struct stat sb; /* ASCII '/' (0x2f) and '0' (0x30) happen to be * adjacent symbols. That makes everything strictly * sorting between file/ and file0 the subdir tree. */ SQL_BEGIN("Query File Table for missing files on the peer", "SELECT filename FROM file WHERE filename = '%s' UNION ALL " "SELECT filename FROM file WHERE filename > '%s/' and filename < '%s0' " "ORDER BY filename;", ue_filename, ue_filename, ue_filename) { const char *fn = url_decode(SQL_V(0)); if (lstat_strict(prefixsubst(fn), &sb) == 0 && csync_check_pure(fn) == 0) textlist_add(&tl, fn, 0); } SQL_END; return tl; } void csync_mark_tl(struct textlist *tl, const char *peername) { struct textlist *t; for (t = tl; t != NULL; t = t->next) csync_mark(t->value, peername, NULL); } struct update_context { char *current_name; const char *peername; const char *ue_peername; const char **patlist; int patnum; int recursive; int dry_run; char *missing_parent_dir; }; enum connection_response conn_hello(struct update_context *c, struct textlist *t) { enum connection_response r = CR_OK; if ( !c->current_name || strcmp(c->current_name, t->value2) ) { csync_debug(3, "Dirty item %s %s %d\n", t->value, t->value2, t->intvalue); conn_printf("HELLO %s\n", url_encode(t->value2)); r = read_conn_status(t->value, c->peername); if (!is_ok_response(r)) return r; free(c->current_name); c->current_name = strdup(t->value2); } return r; } enum connection_response csync_update_tl_mod(struct textlist *tl_mod, struct update_context *c) { struct textlist *t; char *skip_subtree; size_t skip_subtree_len; enum connection_response r = CR_OK; /* If we had a missing_parent_dir before, skip ahead, to make sure we * make progress in case we have more than one missing subtree, * and some conflict or other error when trying to re-create them. */ skip_subtree = c->missing_parent_dir; skip_subtree_len = skip_subtree ? strlen(skip_subtree) : 0; for (t = tl_mod; t != 0; t = t->next) { if (skip_subtree) { int cmp = strncmp(skip_subtree, t->value, skip_subtree_len); if (cmp > 0) continue; if (cmp == 0 && t->value[skip_subtree_len] == '/') { csync_debug(3, "Skipped %s\n", t->value); continue; } skip_subtree = NULL; skip_subtree_len = 0; } r = conn_hello(c, t); if (!is_ok_response(r)) return r; if (!connection_closed_error) r = csync_update_file_mod(c->peername, t->value, t->intvalue, c->dry_run); if (r == CR_ERR_PARENT_DIR_MISSING) { struct textlist *tl = NULL; int filename_length = strlen(t->value); char parent_dirname[filename_length]; split_dirname_basename(parent_dirname, NULL, t->value); csync_debug(1, "missing parent dir : %s\n", parent_dirname); if (c->missing_parent_dir && strcmp(c->missing_parent_dir, parent_dirname) == 0) { /* Don't take action, if we had this same dir * missing on a previous iteration. * Instead skip ahead the todo list. */ skip_subtree = t->value; skip_subtree_len = strlen(skip_subtree); /* change to generic error, in case this was * the last item on the todo list */ r = CR_ERROR; } else { tl = csync_find_files_recursive(parent_dirname); csync_mark_tl(tl, c->peername); textlist_free(tl); free(c->missing_parent_dir); c->missing_parent_dir = strdup(parent_dirname); break; } } } return r; } struct textlist *csync_find_dirty(struct update_context *c) { struct textlist *tl = NULL; SQL_BEGIN("Get files for host from dirty table", "SELECT filename, myname, forced FROM dirty WHERE peername = '%s' " "ORDER by filename ASC", c->ue_peername) { const char *filename = url_decode(SQL_V(0)); int use_this = (c->patnum == 0); int i; for (i=0; i < c->patnum && !use_this; i++) if (compare_files(filename, c->patlist[i], c->recursive)) use_this = 1; if (use_this) textlist_add2(&tl, filename, url_decode(SQL_V(1)), atoi(SQL_V(2))); } SQL_END; return tl; } void csync_update_host_c(struct update_context *c) { struct textlist *tl, *t, *next_t; struct textlist *tl_mod, **last_tn; enum connection_response r = CR_OK; int saved_csync_error_count = csync_error_count; tl = csync_find_dirty(c); tl_mod = NULL; last_tn=&tl; /* just return if there are no files to update */ if ( !tl ) return; if ( connect_to_host(c->peername) ) { csync_error_count++; csync_debug(0, "ERROR: Connection to remote host `%s' failed.\n", c->peername); csync_debug(1, "Host stays in dirty state. " "Try again later...\n"); return; } redo: /* * The SQL statement above creates a linked list. Due to the * way the linked list is created, it has the reversed order * of the sql output. This order is good for removing stuff * (deep entries first) but we need to use the original order * for adding things. * * So I added a 2nd linked list for adding and modifying * files: *tl_mod. Whever a file should be added/modified * it's removed in the *tl linked list and moved to that * other linked list. * */ for (t = tl; t != 0; t = next_t) { struct stat st; next_t = t->next; if ( !lstat_strict(prefixsubst(t->value), &st) != 0 && !csync_check_pure(t->value)) { *last_tn = next_t; t->next = tl_mod; tl_mod = t; } else { r = conn_hello(c, t); if (!is_ok_response(r)) goto ident_failed_1; if (!connection_closed_error) csync_update_file_del(c->peername, t->value, t->intvalue, c->dry_run); ident_failed_1: last_tn=&(t->next); } } r = csync_update_tl_mod(tl_mod, c); textlist_free(tl_mod); textlist_free(tl); if (r == CR_ERR_PARENT_DIR_MISSING) { csync_error_count = saved_csync_error_count; tl = csync_find_dirty(c); tl_mod = NULL; last_tn=&tl; goto redo; } conn_printf("BYE\n"); read_conn_status(0, c->peername);//why is response ignored? conn_close(); } void csync_update_host(const char *peername, const char **patlist, int patnum, int recursive, int dry_run) { struct update_context c = { .current_name = NULL, .peername = peername, .ue_peername = strdup(url_encode(peername)), .patlist = patlist, .patnum = patnum, .recursive = recursive, .dry_run = dry_run, .missing_parent_dir = NULL, }; csync_update_host_c(&c); } void csync_update(const char ** patlist, int patnum, int recursive, int dry_run) { struct textlist *tl = 0, *t; SQL_BEGIN("Get hosts from dirty table", "SELECT peername FROM dirty GROUP BY peername") { textlist_add(&tl, url_decode(SQL_V(0)), 0); } SQL_END; for (t = tl; t != 0; t = t->next) { if (active_peerlist) { int i=0, pnamelen = strlen(t->value); while (active_peerlist[i]) { if ( !strncasecmp(active_peerlist+i, t->value, pnamelen) && (active_peerlist[i+pnamelen] == ',' || !active_peerlist[i+pnamelen]) ) goto found_asactive; while (active_peerlist[i]) if (active_peerlist[i++]==',') break; } continue; } found_asactive: csync_update_host(t->value, patlist, patnum, recursive, dry_run); } textlist_free(tl); } int csync_diff(const char *myname, const char *peername, const char *filename) { FILE *p; void *old_sigpipe_handler; const struct csync_group *g = 0; const struct csync_group_host *h; char buffer[512]; size_t rc; while ( (g=csync_find_next(g, filename)) ) { if ( !g->myname || strcmp(g->myname, myname) ) continue; for (h = g->host; h; h = h->next) if (!strcmp(h->hostname, peername)) { goto found_host_check; } } csync_debug(0, "Host pair + file not found in configuration.\n"); csync_error_count++; return 0; found_host_check: if ( connect_to_host(peername) ) { csync_error_count++; csync_debug(0, "ERROR: Connection to remote host `%s' failed.\n", peername); return 0; } conn_printf("HELLO %s\n", myname); if (!is_ok_response(read_conn_status(NULL, peername))) goto finish; conn_printf("TYPE %s %s\n", url_encode(g->key), url_encode(filename)); if (!is_ok_response(read_conn_status(NULL, peername))) goto finish; /* FIXME * verify type of file first! * (symlink vs. file vs. dir vs. whatever) */ /* avoid unwanted side effects due to special chars in filenames, * pass them in the environment */ snprintf(buffer,512,"%s:%s",myname,filename); setenv("my_label",buffer,1); snprintf(buffer,512,"%s:%s",peername,filename); setenv("peer_label",buffer,1); snprintf(buffer,512,"%s",prefixsubst(filename)); setenv("diff_file",buffer,1); /* XXX no error check on setenv * (could be insufficient space in environment) */ snprintf(buffer, 512, "diff -Nus --label \"$peer_label\" - --label \"$my_label\" \"$diff_file\""); old_sigpipe_handler = signal(SIGPIPE, SIG_IGN); p = popen(buffer, "w"); while ( (rc=conn_read(buffer, 512)) > 0 && fwrite(buffer, 1, rc, p) == rc) ; fclose(p); signal(SIGPIPE, old_sigpipe_handler); if (rc > 0) fprintf(stdout, "*** output to diff truncated\n"); finish: conn_close(); return 0; } int csync_insynctest_readline(char **file, char **checktxt) { char inbuf[2048], *tmp; if (*file) free(*file); if (*checktxt) free(*checktxt); *file = *checktxt = 0; if ( !conn_gets(inbuf, sizeof(inbuf)) ) return 1; if ( inbuf[0] != 'v' ) { if ( !strncmp(inbuf, "OK (", 4) ) { csync_debug(2, "End of query results: %s", inbuf); return 1; } csync_error_count++; csync_debug(0, "ERROR from peer: %s", inbuf); return 1; } tmp = strtok(inbuf, "\t"); if (tmp) *checktxt=strdup(url_decode(tmp)); else { csync_error_count++; csync_debug(0, "Format error in reply: \\t not found!\n"); return 1; } tmp = strtok(0, "\n"); if (tmp) *file=strdup(url_decode(tmp)); else { csync_error_count++; csync_debug(0, "Format error in reply: \\n not found!\n"); return 1; } csync_debug(2, "Fetched tuple from peer: %s [%s]\n", *file, *checktxt); return 0; } int csync_insynctest(const char *myname, const char *peername, int init_run, int auto_diff, const char *filename) { struct textlist *diff_list = 0, *diff_ent; const struct csync_group *g; const struct csync_group_host *h; char *r_file=0, *r_checktxt=0; int remote_reuse = 0, remote_eof = 0; int rel, ret = 1; for (g = csync_group; g; g = g->next) { if ( !g->myname || strcmp(g->myname, myname) ) continue; for (h = g->host; h; h = h->next) if (!strcmp(h->hostname, peername)) goto found_host_check; } csync_debug(0, "Host pair not found in configuration.\n"); csync_error_count++; return 0; found_host_check: if ( connect_to_host(peername) ) { csync_error_count++; csync_debug(0, "ERROR: Connection to remote host `%s' failed.\n", peername); return 0; } conn_printf("HELLO %s\n", myname); if (!is_ok_response(read_conn_status(0, peername))) { csync_debug(0, "ERROR: remote host %s did not accept my identification.\n", peername); csync_error_count++; conn_close(); return 0; } conn_printf("LIST %s %s", peername, filename ? url_encode(filename) : "-"); for (g = csync_group; g; g = g->next) { if ( !g->myname || strcmp(g->myname, myname) ) continue; for (h = g->host; h; h = h->next) if (!strcmp(h->hostname, peername)) goto found_host; continue; found_host: conn_printf(" %s", g->key); } conn_printf("\n"); SQL_BEGIN("DB Dump - File", "SELECT checktxt, filename FROM file %s%s%s ORDER BY filename", filename ? "WHERE filename = '" : "", filename ? url_encode(filename) : "", filename ? "'" : "") { char *l_file = strdup(url_decode(SQL_V(1))), *l_checktxt = strdup(url_decode(SQL_V(0))); if ( csync_match_file_host(l_file, myname, peername, 0) ) { if ( remote_eof ) { got_remote_eof: ret=0; /* (potentially) not in sync */ if (auto_diff) textlist_add(&diff_list, strdup(l_file), 0); else printf("L\t%s\t%s\t%s\n", myname, peername, l_file); if (init_run & 1) csync_mark(l_file, 0, (init_run & 4) ? peername : 0); } else { if ( !remote_reuse ) if ( csync_insynctest_readline(&r_file, &r_checktxt) ) { remote_eof = 1; goto got_remote_eof; } rel = strcmp(l_file, r_file); while ( rel > 0 ) { ret=0; /* not in sync */ if (auto_diff) textlist_add(&diff_list, strdup(r_file), 0); else printf("R\t%s\t%s\t%s\n", myname, peername, r_file); if (init_run & 2) csync_mark(r_file, 0, (init_run & 4) ? peername : 0); if ( csync_insynctest_readline(&r_file, &r_checktxt) ) { remote_eof = 1; goto got_remote_eof; } rel = strcmp(l_file, r_file); } if ( rel < 0 ) { ret=0; /* not in sync */ if (auto_diff) textlist_add(&diff_list, strdup(l_file), 0); else printf("L\t%s\t%s\t%s\n", myname, peername, l_file); if (init_run & 1) csync_mark(l_file, 0, (init_run & 4) ? peername : 0); remote_reuse = 1; } else { remote_reuse = 0; if ( !rel ) { if ( strcmp(l_checktxt, r_checktxt) ) { ret=0; /* not in sync */ if (auto_diff) textlist_add(&diff_list, strdup(l_file), 0); else printf("X\t%s\t%s\t%s\n", myname, peername, l_file); if (init_run & 1) csync_mark(l_file, 0, (init_run & 4) ? peername : 0); } } } } } free(l_checktxt); free(l_file); } SQL_END; if ( !remote_eof ) while ( !csync_insynctest_readline(&r_file, &r_checktxt) ) { ret=0; /* not in sync */ if (auto_diff) textlist_add(&diff_list, strdup(r_file), 0); else printf("R\t%s\t%s\t%s\n", myname, peername, r_file); if (init_run & 2) csync_mark(r_file, 0, (init_run & 4) ? peername : 0); } if (r_file) free(r_file); if (r_checktxt) free(r_checktxt); conn_printf("BYE\n"); read_conn_status(0, peername);//why is response ignored? conn_close(); for (diff_ent=diff_list; diff_ent; diff_ent=diff_ent->next) csync_diff(myname, peername, diff_ent->value); textlist_free(diff_list); return ret; } int csync_insynctest_all(int init_run, int auto_diff, const char *filename) { struct textlist *myname_list = 0, *myname; struct csync_group *g; int ret = 1; if (auto_diff && filename) { struct peer *pl = csync_find_peers(filename, 0); int pl_idx; for (pl_idx=0; pl && pl[pl_idx].peername; pl_idx++) csync_diff(pl[pl_idx].myname, pl[pl_idx].peername, filename); free(pl); return ret; } for (g = csync_group; g; g = g->next) { if ( !g->myname ) continue; for (myname=myname_list; myname; myname=myname->next) if ( !strcmp(g->myname, myname->value) ) goto skip_this_myname; textlist_add(&myname_list, g->myname, 0); skip_this_myname: ; } for (myname=myname_list; myname; myname=myname->next) { struct textlist *peername_list = 0, *peername; struct csync_group_host *h; for (g = csync_group; g; g = g->next) { if ( !g->myname || strcmp(myname->value, g->myname) ) continue; for (h=g->host; h; h=h->next) { for (peername=peername_list; peername; peername=peername->next) if ( !strcmp(h->hostname, peername->value) ) goto skip_this_peername; textlist_add(&peername_list, h->hostname, 0); skip_this_peername: ; } } for (peername=peername_list; peername; peername=peername->next) { csync_debug(1, "Running in-sync check for %s <-> %s.\n", myname->value, peername->value); if ( !csync_insynctest(myname->value, peername->value, init_run, auto_diff, filename) ) ret=0; } textlist_free(peername_list); } textlist_free(myname_list); return ret; } void csync_remove_old() { struct textlist *tl = 0, *t; SQL_BEGIN("Query dirty DB", "SELECT filename, myname, peername FROM dirty") { const struct csync_group *g = 0; const struct csync_group_host *h; const char *filename = url_decode(SQL_V(0)); while ((g=csync_find_next(g, filename)) != 0) { if (!strcmp(g->myname, SQL_V(1))) for (h = g->host; h; h = h->next) { if (!strcmp(h->hostname, SQL_V(2))) goto this_dirty_record_is_ok; } } textlist_add2(&tl, SQL_V(0), SQL_V(2), 0); this_dirty_record_is_ok: ; } SQL_END; for (t = tl; t != 0; t = t->next) { csync_debug(1, "Removing %s (%s) from dirty db.\n", t->value, t->value2); SQL("Remove old file from dirty db", "DELETE FROM dirty WHERE filename = '%s' AND peername = '%s'", t->value, t->value2); } textlist_free(tl); tl = 0; SQL_BEGIN("Query file DB", "SELECT filename FROM file") { if (!csync_find_next(0, url_decode(SQL_V(0)))) textlist_add(&tl, SQL_V(0), 0); } SQL_END; for (t = tl; t != 0; t = t->next) { csync_debug(1, "Removing %s from file db.\n", t->value); SQL("Remove old file from file db", "DELETE FROM file WHERE filename = '%s'", t->value); } textlist_free(tl); } csync2-2.0-22-gce67c55/urlencode.c000066400000000000000000000047521333746731400163720ustar00rootroot00000000000000/* * csync2 - cluster synchronization tool, 2nd generation * Copyright (C) 2004 - 2015 LINBIT Information Technologies GmbH * http://www.linbit.com; see also AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csync2.h" #include #include #include #define RINGBUFF_LEN 10 static char *ringbuff[RINGBUFF_LEN]; int ringbuff_counter = 0; // perl -e 'printf("\\%03o", $_) for(1..040)' | fold -64; echo static char badchars[] = "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020" "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040" "\177\"'%$:|\\"; const char *url_encode(const char *in) { char *out; int i, j, k, len; for (i=len=0; in[i]; i++, len++) for (j=0; badchars[j]; j++) if ( in[i] == badchars[j] ) { len+=2; break; } out = malloc(len + 1); for (i=k=0; in[i]; i++) { for (j=0; badchars[j]; j++) if ( in[i] == badchars[j] ) break; if ( badchars[j] ) { snprintf(out+k, 4, "%%%02X", in[i]); k += 3; } else out[k++] = in[i]; } assert(k==len); out[k] = 0; if ( ringbuff[ringbuff_counter] ) free(ringbuff[ringbuff_counter]); ringbuff[ringbuff_counter++] = out; if ( ringbuff_counter == RINGBUFF_LEN ) ringbuff_counter=0; return out; } const char *url_decode(const char *in) { char *out, num[3]="XX"; int i, k, len; for (i=len=0; in[i]; i++, len++) if ( in[i] == '%' && in[i+1] && in[i+2] ) i+=2; out = malloc(len + 1); for (i=k=0; in[i]; i++) if ( in[i] == '%' && in[i+1] && in[i+2] ) { num[0] = in[++i]; num[1] = in[++i]; out[k++] = strtol(num, 0, 16); } else out[k++] = in[i]; assert(k==len); out[k] = 0; if ( ringbuff[ringbuff_counter] ) free(ringbuff[ringbuff_counter]); ringbuff[ringbuff_counter++] = out; if ( ringbuff_counter == RINGBUFF_LEN ) ringbuff_counter=0; return out; }