/* * 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