/*
* msync.c
* Copyright (C) 2008-2012 KLab Inc.
*/
#include "makuosan.h"
typedef struct msyncdata
{
int s; /* */
int loopflag; /* */
int loglevel; /* */
int sendflag; /* */
int delflag; /* */
int grpflag; /* */
char scfile[256]; /* */
char passwd[256]; /* */
char target[256]; /* */
char mcmd[256]; /* */
char mopt[256]; /* */
char sopt[256]; /* */
char path[PATH_MAX]; /* */
excludeitem *exclude; /* */
} msyncdata;
void version()
{
printf("msync version %s (CLI for makuosan)\n", PACKAGE_VERSION);
}
void usage()
{
version();
printf("usage: msync [OPTION] [FILENAME]\n");
printf("\n");
printf(" OPTION\n");
printf(" --status # show makuosan status\n");
printf(" --members # show makuosan members\n");
printf(" --check # file check use md5\n");
printf(" --delete # \n");
printf(" --sync # \n");
printf(" --exclude=PATTERN # \n");
printf(" --exclude-from=FILE # \n");
printf("\n");
printf(" -l LOGLEVEL(0-9) # log level select. default=0\n");
printf(" -c MSYNC_TARGET # \n");
printf(" -f SCRIPT_FILE # \n");
printf(" -t HOSTNAME # distnation hostname\n");
printf(" -v # log level increment\n");
printf(" -n # dry run\n");
printf(" -r # recurse into directories\n");
printf("\n");
printf(" MSYNC_TARGET\n");
printf(" tcp:HOST:PORT ex) tcp:127.0.0.1:5000\n");
printf(" unix:SOCKET ex) unix:makuosan.sock\n");
printf("\n");
printf(" SCRIPT_FILE\n");
printf(" (It writes later)\n");
printf("\n");
}
excludeitem *add_exclude(msyncdata *md, char *pattern)
{
excludeitem *item = malloc(sizeof(excludeitem));
item->pattern = malloc(strlen(pattern) + 1);
strcpy(item->pattern, pattern);
item->prev = NULL;
item->next = NULL;
if(md->exclude){
md->exclude->prev = item;
item->next = md->exclude;
}
md->exclude = item;
return(item);
}
int connect_socket_tcp(char *host, char *port)
{
int s;
struct addrinfo hint;
struct addrinfo *res;
if(!host || !port){
return(-1);
}
memset(&hint, 0, sizeof(struct addrinfo));
hint. ai_family = AF_INET;
hint.ai_socktype = SOCK_STREAM;
hint.ai_protocol = IPPROTO_TCP;
if(getaddrinfo(host, port, &hint, &res)){
return(-1);
}
if(!res){
return(-1);
}
s = socket(AF_INET, SOCK_STREAM, 0);
if(s == -1){
freeaddrinfo(res);
return(-1);
}
if(connect(s, res->ai_addr, res->ai_addrlen) == -1){
freeaddrinfo(res);
close(s);
return(-1);
}
freeaddrinfo(res);
return(s);
}
int connect_socket_unix(char *path)
{
int s;
struct sockaddr_un sa;
if(!path){
return(-1);
}
if(strlen(path) >= sizeof(sa.sun_path)){
return(-1);
}
s = socket(AF_UNIX, SOCK_STREAM, 0);
if(s == -1){
return(-1);
}
sa.sun_family = AF_UNIX;
strcpy(sa.sun_path,path);
if(connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1){
close(s);
return(-1);
}
return(s);
}
int connect_socket(char *target)
{
char *h;
char *p;
char buff[256];
strcpy(buff, target);
p = strtok(buff,":");
if(!p){
usage();
exit(1);
}
if(!strcmp(p, "tcp")){
h = strtok(NULL,":");
p = strtok(NULL,":");
return(connect_socket_tcp(h,p));
}
if(!strcmp(p, "unix")){
p = strtok(NULL,":");
return(connect_socket_unix(p));
}
return(connect_socket_unix(buff));
}
int writeline(int s, char *buff)
{
int r;
int clen;
int size;
clen = strlen(buff);
size = clen;
while(size){
r = write(s, buff + clen - size, size);
if(r == -1){
return(-1);
}
size -= r;
}
return(0);
}
int check_prompt(int s, char *buff, char *passwd)
{
if(!strcmp(buff, "> ")){
return(1);
}
if(!strcmp(buff, "password: \x1b]E") && passwd){
writeline(s, passwd);
writeline(s, "\r\n");
return(2);
}
return(0);
}
int readline(int s, char *buff, int size, int prompt, char *passwd)
{
int r = 0;
char d = 0;
char *p = buff;
while(p < buff + size){
*p = 0;
if(prompt){
switch(check_prompt(s, buff, passwd)){
case 1:
return(-2);
case 2:
p = buff;
continue;
}
}
r = read(s, &d, 1);
if(r == 0){
return(p - buff);
}
if(r == -1){
return(-1);
}
if(d == '\n'){
if(p != buff){
return(p - buff);
}
}else{
if(d != '\r'){
*(p++) = d;
}
}
}
return(-1); /* over flow */
}
int wait_prompt(int s, char *passwd, int view, int *line){
int r;
char buff[8192];
while((r = readline(s, buff, sizeof(buff), 1, passwd))){
if(r == -1){
/* read error */
r = -1;
break;
}
if(r == -2){
/* return prompt */
r = 1;
break;
}
if(!strcmp(buff, "alive")){
continue;
}
if(line){
(*line)++;
}
if(view){
if(!memcmp(buff, "error:", 6)){
fprintf(stderr, "%s\n", buff);
}else{
fprintf(stdout, "%s\n", buff);
}
}
}
return(r);
}
int makuo(int s, char *c, int view)
{
int r;
int line = 1;
char buff[256];
if(sizeof(buff) < strlen(c) + 2){
fprintf(stderr, "error: command too long\n");
return(-1);
}
sprintf(buff, "%s\r\n", c);
if(writeline(s, buff) == -1){
fprintf(stderr, "error: can't write socket\n");
return(-1);
}
r = wait_prompt(s, NULL, view, &line);
if(r == -1){
fprintf(stderr, "error: can't read socket\n");
return(-1);
}
if(r == 0){
return(0);
}
return(line);
}
void makuo_aliveon(msyncdata *md)
{
int r;
char cmd[256];
struct timeval tv;
sprintf(cmd, "alive on");
r = makuo(md->s, cmd, 0);
if(r == 0){
exit(1);
}
if(r == -1){
exit(1);
}
if(r == 1){
tv.tv_sec = 30;
tv.tv_usec = 0;
}else{
tv.tv_sec = 0;
tv.tv_usec = 0;
}
setsockopt(md->s, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv));
}
void makuo_log(msyncdata *md)
{
int r;
char cmd[256];
sprintf(cmd, "loglevel %d", md->loglevel);
r = makuo(md->s, cmd, 0);
if(r == 0){
fprintf(stderr, "error: remote close\n");
exit(1);
}
if(r == -1){
exit(1);
}
}
void makuo_exclude(msyncdata *md)
{
int r;
char cmd[1024];
excludeitem *item;
for(item=md->exclude;item;item=item->next){
sprintf(cmd, "exclude add %s", item->pattern);
r = makuo(md->s, cmd, 0);
if(r == 0){
fprintf(stderr, "error: makuosan remote close. (%s)\n", cmd);
exit(1);
}
if(r == -1){
fprintf(stderr, "error: makuosan socket error. (%s)\n", cmd);
exit(1);
}
}
}
int makuo_exec(int s, char *cmd)
{
int r = makuo(s, cmd, 1);
if(r == 0){
fprintf(stderr, "error: makuosan remote close. (%s)\n", cmd);
return(1);
}
if(r == -1){
fprintf(stderr, "error: makuosan socket error. (%s)\n", cmd);
return(1);
}
return(0);
}
void makuo_send(msyncdata *md)
{
char cmd[1024];
if(md->delflag){
sprintf(cmd, "dsync%s %s", md->mopt, md->path);
if(makuo_exec(md->s, cmd)){
close(md->s);
exit(1);
}
}
if(md->sendflag){
sprintf(cmd, "%s%s%s %s", md->mcmd, md->mopt, md->sopt, md->path);
}else{
sprintf(cmd, "%s%s %s", md->mcmd, md->mopt, md->path);
}
if(makuo_exec(md->s, cmd)){
close(md->s);
exit(1);
}
}
int makuo_quit(msyncdata *md)
{
int r = makuo(md->s, "quit", 0);
close(md->s);
if(r == 0){
return(0); /* success */
}
if(r == -1){
return(1);
}
fprintf(stderr, "quit error?!\n");
return(1);
}
int exclude_from(msyncdata *md, char *filename)
{
int f;
int r;
char line[256];
if(!strcmp(filename, "-")){
f = dup(0);
}else{
f = open(filename, O_RDONLY);
}
if(f == -1){
fprintf(stderr,"can't open: %s\n", filename);
return(1);
}
while((r = readline(f, line, sizeof(line), 0, NULL))){
if(r == -1){
fprintf(stderr, "file read error: %s\n", filename);
close(f);
return(1);
}
if((*line != '\r') && (*line != '\n') && (*line !=0)){
add_exclude(md, line);
}
}
close(f);
return(0);
}
int makuo_file(msyncdata *md)
{
int f;
int r;
char line[256];
char *filename;
filename = md->scfile;
if(!strlen(filename)){
return(0);
}
if(!strcmp(filename, "-")){
/* f = stdin */
f = dup(0);
}else{
f = open(filename, O_RDONLY);
}
if(f == -1){
fprintf(stderr,"can't open: %s\n", filename);
return(1);
}
/* command read loop */
while((r = readline(f, line, sizeof(line), 0, NULL))){
if(r == -1){
fprintf(stderr, "file read error: %s\n", filename);
break;
}
if(makuo_exec(md->s, line)){
close(f);
exit(1);
}
}
close(f);
return(1);
}
int loadpass(char *filename, char *passwd, int size)
{
int f;
f = open(filename, O_RDONLY);
if(f == -1){
fprintf(stderr, "file open error %s\n", filename);
return(-1);
}
if(readline(f, passwd, size, 0, NULL) == -1){
fprintf(stderr, "file read error %s\n", filename);
close(f);
return(-1);
}
close(f);
return(0);
}
void get_envopt(msyncdata *md)
{
char *p;
if((p = getenv("MSYNC_TARGET"))){
if(strlen(p) < sizeof(md->target)){
strcpy(md->target, p);
}else{
fprintf(stderr, "MSYNC_TARGET too long. %s\n", p);
exit(1);
}
}
}
struct option *optinit()
{
static struct option longopt[10];
longopt[0].name = "help";
longopt[0].has_arg = 0;
longopt[0].flag = NULL;
longopt[0].val = 'h';
longopt[1].name = "status";
longopt[1].has_arg = 0;
longopt[1].flag = NULL;
longopt[1].val = 'S';
longopt[2].name = "members";
longopt[2].has_arg = 0;
longopt[2].flag = NULL;
longopt[2].val = 'M';
longopt[3].name = "check";
longopt[3].has_arg = 0;
longopt[3].flag = NULL;
longopt[3].val = 'C';
longopt[4].name = "exclude";
longopt[4].has_arg = 1;
longopt[4].flag = NULL;
longopt[4].val = 'E';
longopt[5].name = "exclude-from";
longopt[5].has_arg = 1;
longopt[5].flag = NULL;
longopt[5].val = 'F';
longopt[6].name = "delete";
longopt[6].has_arg = 0;
longopt[6].flag = NULL;
longopt[6].val = 'D';
longopt[7].name = "sync";
longopt[7].has_arg = 0;
longopt[7].flag = NULL;
longopt[7].val = 'd';
longopt[8].name = "version";
longopt[8].has_arg = 0;
longopt[8].flag = NULL;
longopt[8].val = 'V';
longopt[9].name = NULL;
longopt[9].has_arg = 0;
longopt[9].flag = NULL;
longopt[9].val = 0;
return(longopt);
}
void parse_opt(int argc, char *argv[], struct option *opt, msyncdata *md)
{
int r;
while((r = getopt_long(argc, argv, "g:c:f:t:K:l:hvrnV", opt, NULL)) != -1){
switch(r){
case 'h':
usage();
exit(0);
case 'V':
version();
exit(0);
case 'D':
md->delflag = 1;
break;
case 'd':
strcpy(md->mcmd, "sync");
break;
case 'S':
strcpy(md->mcmd, "status");
md->loopflag = 0;
md->sendflag = 0;
break;
case 'M':
strcpy(md->mcmd, "members");
md->loopflag = 0;
md->sendflag = 0;
break;
case 'C':
strcpy(md->mcmd, "check");
md->sendflag = 0;
break;
case 'E':
add_exclude(md, optarg);
break;
case 'F':
if(exclude_from(md, optarg)){
exit(1);
}
break;
case 'r':
strcat(md->mopt," -r");
break;
case 'n':
strcat(md->mopt," -n");
break;
case 't':
strcat(md->mopt," -t ");
strcat(md->mopt,optarg);
break;
case 'g':
md->grpflag = 1;
strcat(md->sopt," -g ");
strcat(md->sopt,optarg);
break;
case 'v':
md->loglevel++;
break;
case 'l':
md->loglevel = atoi(optarg);
break;
case 'f':
if(strlen(optarg) < sizeof(md->scfile)){
strcpy(md->scfile, optarg);
}else{
fprintf(stderr, "filename too long\n");
exit(1);
}
break;
case 'c':
if(strlen(optarg) < sizeof(md->target)){
strcpy(md->target, optarg);
}else{
fprintf(stderr, "target too long\n");
exit(1);
}
break;
case 'K':
if(loadpass(optarg, md->passwd, sizeof(md->passwd)) == -1){
exit(1);
}
break;
case '?':
usage();
exit(1);
break;
}
}
if(md->delflag && !md->sendflag){
usage();
exit(1);
}
if(md->grpflag && !md->sendflag){
usage();
exit(1);
}
if(argc == optind){
md->loopflag = 0;
}
}
int connect_wait(msyncdata *md)
{
int r;
r = wait_prompt(md->s, md->passwd, 0, NULL);
if(r == 0){
fprintf(stderr, "remote socket close\n");
return(1);
}
if(r == -1){
fprintf(stderr, "socket read error\n");
return(1);
}
return(0);
}
void connect_target(msyncdata *md)
{
struct timeval tv;
md->s = connect_socket(md->target);
if(md->s == -1){
fprintf(stderr, "can't connect %s\n", md->target);
exit(1);
}
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt(md->s, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv));
if(connect_wait(md)){
close(md->s);
exit(1);
}
}
void msync_init(msyncdata *md)
{
memset(md, 0, sizeof(msyncdata));
md->loopflag = 1;
md->sendflag = 1;
strcpy(md->mcmd, "send");
strcpy(md->target, "tcp:127.0.0.1:5000");
}
int main(int argc, char *argv[])
{
int i;
msyncdata md;
if(argc == 1){
usage();
exit(1);
}
msync_init(&md);
get_envopt(&md);
parse_opt(argc, argv, optinit(), &md);
connect_target(&md);
makuo_aliveon(&md);
makuo_log(&md);
makuo_exclude(&md);
if(!makuo_file(&md)){
if(!md.loopflag){
md.path[0] = 0;
makuo_send(&md);
}else{
for(i=optind;i<argc;i++){
strcpy(md.path, argv[i]);
makuo_send(&md);
}
}
}
return(makuo_quit(&md));
}