synk

synchronize files between hosts
Log | Files | Refs | README | LICENSE

commit 408ba38ec5c0c74adec071d58f3219471cedfb70
parent 737370d683dbd0e04feae39f454169158a27091b
Author: Willy <willyatmailoodotorg>
Date:   Wed Sep  7 14:09:07 +0200

Merge branch 'loadconf'

Diffstat:
README | 19+++++++++++++++++--
config.mk | 1+
mkfile | 9++++++---
parse.y | 396+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
synk.c | 90+++++++++++++++++++++++++++-----------------------------------------------------
synk.h | 37+++++++++++++++++++++++++++++++++++++
6 files changed, 487 insertions(+), 65 deletions(-)
diff --git a/README b/README @@ -17,7 +17,11 @@ between multiple peers: usage ----- - $ synk -v -h keel.z3bra.org -h phobos.z3bra.org $HOME/file + $ cat /etc/synk.conf + peer keel.z3bra.org + peer phobos.z3bra.org + + $ synk -v $HOME/file peer: localhost /home/z3bra/file c8e37eb 1473157623 peer: phobos.z3bra.org /home/z3bra/file 6d4af03 1473157643 peer: keel.z3bra.org /home/z3bra/file 005f686 1473157705 @@ -25,11 +29,22 @@ usage synk: rsync -azEq --delete keel.z3bra.org:/home/z3bra/file /home/z3bra/file synk: ssh keel.z3bra.org rsync -azEq --delete /home/z3bra/file phobos.z3bra.org:/home/z3bra/file - $ synk -v -h keel.z3bra.org -h phobos.z3bra.org $HOME/file + $ synk -v $HOME/file peer: localhost /home/z3bra/file 005f686 1473157705 peer: phobos.z3bra.org /home/z3bra/file 005f686 1473157705 peer: keel.z3bra.org /home/z3bra/file 005f686 1473157705 +syntax +------ + +The configuration file (default: /etc/synk.conf) contain the peers +definition, one per line, in the following format: + + peer <host> [port] + +If no port is specified, the default value (9723) will be used. Hosts +can be specified either by their fqdn, or ip address. + how it works ------------ diff --git a/config.mk b/config.mk @@ -2,6 +2,7 @@ VERSION = 0.0 CC = cc LD = ${CC} +YACC = yacc PREFIX = /usr/local MANDIR = ${PREFIX}/man diff --git a/mkfile b/mkfile @@ -1,13 +1,16 @@ <config.mk -synk: synk.o sha512.o +synk: y.tab.o synk.o sha512.o $LD -o $target $prereq $LDFLAGS $LIBS -%.o: %.c +%.o: %.c synk.h $CC $CFLAGS -c $stem.c -o $stem.o +y.tab.c: parse.y + $YACC $prereq + clean:V: - rm -f *.o synk + rm -f *.o synk y.tab.c install:V: all mkdir -p ${DESTDIR}${PREFIX}/bin diff --git a/parse.y b/parse.y @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2006 Bob Beck <beck@openbsd.org> + * Copyright (c) 2002-2006 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include <errno.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "synk.h" + +static TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; + +static struct file *pushfile(const char *); +static int popfile(void); +static int yyparse(void); +static int yylex(void); +static int yyerror(const char *, ...); +static int kwcmp(const void *, const void *); +static int lookup(char *); +static int lgetc(int); +static int lungetc(int); +static int findeol(void); + +static struct peers_t *peers = NULL; + +typedef struct { + union { + int number; + char *string; + } v; + int lineno; +} YYSTYPE; +%} + +%token PEER ERROR +%token <v.string> STRING +%token <v.number> NUMBER +%% + +grammar : /* empty */ + | grammar '\n' + | grammar main '\n' + | grammar error '\n' { + file->errors++; + } + ; + +main : PEER STRING NUMBER { + addpeer(peers, $2, $3); + } + | PEER STRING { + addpeer(peers, $2, DEFPORT); + } + ; +%% + +struct keywords { + const char *name; + int val; +}; + +static int +yyerror(const char *fmt, ...) +{ + char buf[512]; + va_list ap; + + file->errors++; + va_start(ap, fmt); + if (vsnprintf(buf, sizeof(buf), fmt, ap) < 0) + perror("vsnprintf"); + va_end(ap); + fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, buf); + return 0; +} + +static int +kwcmp(const void *k, const void *e) +{ + return strcmp(k, ((const struct keywords *)e)->name); +} + +static int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "peer", PEER } + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kwcmp); + + if (p) + return p->val; + else + return STRING; +} + +#define MAXPUSHBACK 128 + +static unsigned char *parsebuf; +static int parseindex; +static unsigned char pushback_buffer[MAXPUSHBACK]; +static int pushback_index = 0; + +static int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return c; + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return pushback_buffer[--pushback_index]; + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return EOF; + return quotec; + } + return c; + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return EOF; + c = getc(file->stream); + } + return c; +} + +static int +lungetc(int c) +{ + if (c == EOF) + return EOF; + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return c; + } + if (pushback_index < MAXPUSHBACK-1) + return pushback_buffer[pushback_index++] = c; + else + return EOF; +} + +static int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return ERROR; +} + +static int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return 0; + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return 0; + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') + continue; + else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return findeol(); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + *p++ = c; + } + yylval.v.string = strdup((char *)buf); + if (!yylval.v.string) + perror("strdup"); + return STRING; + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + + *p = '\0'; + yylval.v.number = strtoll((char *)buf, NULL, 10); + if (errno == ERANGE) { + yyerror("\"%s\" invalid number: %s", + buf, strerror(errno)); + return findeol(); + } + return NUMBER; + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return c; + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup((char *)buf)) == STRING) + if (!(yylval.v.string = strdup((char *)buf))) + perror("strdup"); + return token; + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return 0; + return c; +} + +static struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if (!(nfile = calloc(1, sizeof(struct file)))) + return NULL; + if (!(nfile->name = strdup(name))) { + free(nfile); + return NULL; + } + if (!(nfile->stream = fopen(nfile->name, "r"))) { + free(nfile->name); + free(nfile); + return NULL; + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return nfile; +} + +static int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry))) + prev->errors += file->errors; + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return file ? 0 : EOF; +} + +int +parseconf(struct peers_t *plist, const char *filename) +{ + int errors = 0; + + if (!(file = pushfile(filename))) { + fprintf(stderr, "failed to open %s\n", filename); + return -1; + } + topfile = file; + + peers = plist; + + yyparse(); + errors = file->errors; + popfile(); + + if (errors != 0) + return -1; + + return errors != 0 ? -1 : 0; +} diff --git a/synk.c b/synk.c @@ -1,5 +1,4 @@ #include <errno.h> -#include <limits.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> @@ -7,8 +6,6 @@ #include <string.h> #include <netdb.h> #include <unistd.h> -#include <arpa/inet.h> -#include <sys/queue.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> @@ -16,41 +13,11 @@ #include "arg.h" #include "sha512.h" +#include "synk.h" #define IS_LOOPBACK(p) ((p)->peer.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) #define log(l,...) if(verbose>=l){printf(__VA_ARGS__);} -#define SERVER_HOST "127.0.0.1" -#define SERVER_PORT 9723 - -#define TIMESTAMP_MAX 19 /* length of LONG_MAX */ -#define CONNECTION_MAX 1 -#define RECVSIZ 512 -#define TIMEOUT 5 -#define RETRY 8 - -/* hold a socket connection, used to pass a connection to a thread */ -struct client_t { - int fd; - struct in_addr inet; -}; - -/* metadata informations about a file, to decide about the synkro state */ -struct metadata_t { - char path[PATH_MAX]; - unsigned char hash[64]; - long mtime; -}; - -/* singly-linked list for all the nodes that should be in synk */ -struct peer_t { - char host[HOST_NAME_MAX]; - struct metadata_t meta; - struct sockaddr_in peer; - SLIST_ENTRY(peer_t) entries; -}; -SLIST_HEAD(peers_t, peer_t); - /* different operationnal mode for TCP connection */ enum { SYNK_CLIENT, @@ -63,24 +30,23 @@ enum { LOG_DEBUG = 2, }; -void usage(char *name); -char *echo(char * []); -char **concat(int, ...); - -long gettimestamp(const char *path); -struct in_addr *getinetaddr(char *); -struct metadata_t *getmetadata(const char *); -struct peer_t *addpeer(struct peers_t *, char *, in_port_t); -struct peer_t *freshestpeer(struct peers_t *); -int getpeermeta(struct peer_t *, struct metadata_t *); -int flushpeers(struct peers_t *); -int spawnremote(struct peers_t *); -int uptodate(struct peers_t *); -int dosync(struct peer_t *master, struct peer_t *slave); -int syncwithmaster(struct peer_t *master, struct peers_t *plist); -int syncfile(struct peers_t *, const char *); -int sendmetadata(struct client_t *); -int waitclient(in_addr_t, in_port_t); +static void usage(char *name); +static char *echo(char * []); +static char **concat(int, ...); + +static long gettimestamp(const char *path); +static struct in_addr *getinetaddr(char *); +static struct metadata_t *getmetadata(const char *); +static struct peer_t *freshestpeer(struct peers_t *); +static int getpeermeta(struct peer_t *, struct metadata_t *); +static int flushpeers(struct peers_t *); +static int spawnremote(struct peers_t *); +static int uptodate(struct peers_t *); +static int dosync(struct peer_t *master, struct peer_t *slave); +static int syncwithmaster(struct peer_t *master, struct peers_t *plist); +static int syncfile(struct peers_t *, const char *); +static int sendmetadata(struct client_t *); +static int waitclient(in_addr_t, in_port_t); const char *rsync_cmd[] = { "rsync", "-azEq", "--delete", NULL }; const char *ssh_cmd[] = { "ssh", NULL }; @@ -90,7 +56,7 @@ int verbose = LOG_NONE; void usage(char *name) { - fprintf(stderr, "usage: %s [-vs] [-p PORT] -h HOST [FILE..]\n", name), + fprintf(stderr, "usage: %s [-vs] [-f FILE] [-p PORT] -h HOST [FILE..]\n", name); exit(1); } @@ -288,11 +254,11 @@ getpeermeta(struct peer_t *clt, struct metadata_t *local) return -1; } - for (i=0; i<RETRY; i++) { + for (i=0; i<MAXRETRY; i++) { if (!connect(cfd, (struct sockaddr *) &(clt->peer), sizeof(clt->peer))) break; - if (errno != ECONNREFUSED || i+1 >= RETRY) { + if (errno != ECONNREFUSED || i+1 >= MAXRETRY) { fprintf(stderr, "%s: %s\n", inet_ntoa(clt->peer.sin_addr), strerror(errno));; return -1; } @@ -307,7 +273,7 @@ getpeermeta(struct peer_t *clt, struct metadata_t *local) /* ... which should return the metadata for this file */ len = 0; while (len < (ssize_t)sizeof(struct metadata_t)) { - if ((r = read(cfd, (unsigned char *)&(clt->meta) + len, RECVSIZ)) < 0) { + if ((r = read(cfd, (unsigned char *)&(clt->meta) + len, RCVBUFSZ)) < 0) { perror("read"); return -1; } @@ -391,7 +357,7 @@ waitclient(in_addr_t host, in_port_t port) return -1; } - if (listen(sfd, CONNECTION_MAX) < 0) { + if (listen(sfd, MAXCONNECT) < 0) { perror("listen"); return -1; } @@ -569,8 +535,9 @@ int main(int argc, char *argv[]) { char *argv0, *fn; + char config[PATH_MAX] = PATHCONFIG; char *hostname = NULL; - in_port_t port = SERVER_PORT; + in_port_t port = DEFPORT; uint8_t mode = SYNK_CLIENT; struct peers_t plist; @@ -578,6 +545,9 @@ main(int argc, char *argv[]) addpeer(&plist, "localhost", 0); ARGBEGIN{ + case 'f': + strncpy(config, EARGF(usage(argv0)), PATH_MAX); + break; case 'h': hostname = EARGF(usage(argv0)); if (mode == SYNK_CLIENT) @@ -589,7 +559,7 @@ main(int argc, char *argv[]) }ARGEND; if (hostname == NULL) - usage(argv0); + parseconf(&plist, config); switch(mode) { case SYNK_CLIENT: @@ -600,7 +570,7 @@ main(int argc, char *argv[]) flushpeers(&plist); break; case SYNK_SERVER: - alarm(TIMEOUT); + alarm(SERVERTIMEO); waitclient(getinetaddr(hostname)->s_addr, port); break; } diff --git a/synk.h b/synk.h @@ -0,0 +1,37 @@ +#include <arpa/inet.h> +#include <sys/queue.h> +#include <limits.h> + +#define DEFADDR "127.0.0.1" +#define DEFPORT 9723 +#define SERVERTIMEO 5 /* in seconds */ +#define RCVBUFSZ 512 +#define UTSLEN 19 +#define MAXCONNECT 1 +#define MAXRETRY 8 +#define PATHCONFIG "/etc/synk.conf" + +/* hold a socket connection, used to pass a connection to a thread */ +struct client_t { + int fd; + struct in_addr inet; +}; + +/* metadata informations about a file, to decide about the synkro state */ +struct metadata_t { + char path[PATH_MAX]; + unsigned char hash[64]; + long mtime; +}; + +/* singly-linked list for all the nodes that should be in synk */ +struct peer_t { + char host[HOST_NAME_MAX]; + struct metadata_t meta; + struct sockaddr_in peer; + SLIST_ENTRY(peer_t) entries; +}; +SLIST_HEAD(peers_t, peer_t); + +struct peer_t *addpeer(struct peers_t *, char *, in_port_t); +int parseconf(struct peers_t *, const char *);