#!/usr/bin/gawk -f # # --------------------------------------------------------------------- # Part I - FTP client copied from ftpclient script. # function noctrl(line) { sub(/[ \t\r\n]+$/, "", line); return (line); } function cfgets(ftpd, line) { if (ftpd |& getline line > 0) { line = noctrl(line); if (debug != 0) printf (">>> %s\n", line) >>STDERR; return (line); } return ("\001"); } function cfputs(ftpd, line) { line = noctrl(line); if (debug != 0) printf ("<<< %s\n", line) >>STDERR; printf ("%s\r\n", line) |& ftpd; fflush(); return (0); } function cfputc(ftpd, cmd, arg, result, k, rc, line, fatal) { if (cmd != "") { line = cmd; if (arg != "") line = cmd " " arg; cfputs(ftpd, line); } rc = 0; while (1) { if ((line = cfgets(ftpd)) == "\001") break; if (rc == 0) rc = line+0; if (line+0 == rc && line ~ /^[0-9]/) { if (length(line) <= 3 || substr(line, 4, 1) == " ") break; } } fatal = 1; if (result < 0) { result = -result; fatal = 0; } rc = toupper(rc); if (result != "" && rc != result) { if (fatal == 1) { printf ("%s: protocol error: %s\n", program, line) >>STDERR; exit (1); } } return (line); } function doport(ftpd, options, local, x, high, low) { portcmd = sprintf ("connect -a %s: %s", interface, options); portcmd |& getline local; split(local, x, ":"); high = int(x[2] / 256); low = x[2] - high * 256; local = x[1] "," high "," low; gsub(/\./, ",", local); cfputc(ftpd, "PORT", local, 200); return (portcmd); } function dolist(ftpd, cmd, dir) { cfputc(ftpd, "TYPE", "A", 200); portcmd = doport(ftpd, ""); cfputc(ftpd, (cmd != "NLST")? "LIST": "NLST", dir, 150); portcmd |& getline line; while (portcmd |& getline line) { noctrl(line); printf ("%s\n", line); } close (portcmd); cfputc(ftpd, "", "", 226); return (0); } function doretr(ftpd, mode, remotename, localname) { if (localname == "") localname = remotename; cfputc(ftpd, "TYPE", mode, 200); portcmd = doport(ftpd, mode == "A"? "": sprintf("-w '%s'", localname)); line = cfputc(ftpd, "RETR", remotename, 150); portcmd |& getline line; if (mode != "A") { portcmd |& getline line; } else { while (portcmd |& getline line) { noctrl(line); printf ("%s\n", line) >localfile; } close (localfile); } close (portcmd); cfputc(ftpd, "", "", 226); return (0); } function dostor(ftpd, mode, localname, remotename) { if (remotename == "") remotename = localname; cfputc(ftpd, "TYPE", mode, 200); portcmd = doport(ftpd, sprintf ("-r '%s'", localname)); cfputc(ftpd, "STOR", remotename, 150); portcmd |& getline line; portcmd |& getline line; close (portcmd); cfputc(ftpd, "", "", 226); return (0); } function dorename(ftpd, oldname, newname) { if (cfputc(ftpd, "RNFR", oldname, 350) + 0 == 350) cfputc(ftpd, "RNTO", newname, 250); return (0); } function dodele(ftpd, remotename) { cfputc(ftpd, "DELE", remotename, 250); return (0); } function domkd(ftpd, dir) { cfputc(ftpd, "MKD", dir, 257); return (0); } function dormd(ftpd, dir) { cfputc(ftpd, "RMD", dir, 250); return (0); } function docwd(ftpd, dir) { cfputc(ftpd, "CWD", dir, 250); return (0); } function dopwd(ftpd, dir, line) { line = cfputc(ftpd, "PWD", "", 257); if (line + 0 == 257) { sub(/^[^ ]+ +/, "", line); if (substr(line, 1, 1) != "\"") sub(/ .*$/, "", line); else { line = substr(line, 2); sub(/".*$/, "", line); } printf ("%s\n", line); } return (0); } # --------------------------------------------------------------------- # Part II - Syncing # function excludefile(file) { if (file ~ conflictchar || file ~ /\t/) return (1); else if (file == "." || file == ".." || file ~ /\.swp$|~$/) return (1); else if (file ~ /;'"/) return (1); else if (file ~ /^\.sync[\.-]/) return (1); else if (file ~ /^\./ && includedots == 0) return (1); else if (file ~ / / && allowblanks == 0) return (1); return (0); } function getpeerinfo(ftpd, file, line, data) { if ((line = cfputc(ftpd, "SIZE", file, -213))+0 != 213) return (""); sub(/^[^ \t]+[ \t]+/, "", line); data = line; if ((line = cfputc(ftpd, "MDTM", file, -213))+0 != 213) return (""); sub(/^[^ \t]+[ \t]+/, "", line); sub(/\..*$/, "", line); data = data " " line; return (data); } function readserverinfo(ftpd, dir, list, file, line, data, dirlen) { delete list; # # Retrieve the list of all files and directories ... # portcmd = doport(ftpd, ""); cfputc(ftpd, "NLST", ".", 150); portcmd |& getline line; dirlen = 0; if (dir != "") { dir = dir "/"; dirlen = length(dir); } while (portcmd |& getline file) { file = noctrl(file); if (dirlen > 0 && substr(file, 1, dirlen) == dir) file = substr(file, dirlen + 1); if (excludefile(file)) continue; list[file] = ""; } close (portcmd); cfputc(ftpd, "", "", 226); # # ... and collect SIZE and MDTM for each file. # for (file in list) { if ((line = cfputc(ftpd, "SIZE", file, -213))+0 != 213) { delete list[file]; continue; } else { sub(/^[^ \t]+[ \t]+/, "", line); data = line; } if ((line = cfputc(ftpd, "MDTM", file, -213))+0 != 213) { delete list[file]; continue; } else { sub(/^[^ \t]+[ \t]+/, "", line); data = data " " line; } list[file] = data; } return (0); } function doconnect(ftpserver, username, password, server) { server = sprintf ("connect -x %s", ftpserver); interface = cfgets(server); sub(/:.*$/, "", interface); if (debug != 0) printf ("local interface is %s\n", interface); cfgets(server); # peer information cfputc(server, "", "", 220); # server greeting cfputc(server, "USER", username, 331); cfputc(server, "PASS", password, 230); return (server); } function getnodeinfo(filename) { if (stat(filename, sbuf) != 0) return (""); return (sbuf["size"] " " strftime("%Y%m%d%H%M%S", sbuf["mtime"])); } function readsyncinfo(filename, list, line, x) { while (getline line 0) { split(line, x, /\t+/); list[x[1]] = x[2]; } close(filename); return (0); } function writesyncinfo(filename, list, n, line, x) { for (file in list) printf ("%s\t%s\n", file, list[file]) >filename; close(filename); return (0); } function computestatus(status, prev, current, file) { for (file in prev) { if (! (file in current)) status[file] = "d"; else if (prev[file] == current[file]) status[file] = "u"; else status[file] = "c"; } for (file in current) { if (! (file in prev)) status[file] = "c"; } return (0); } function readconfig(filename, pw, sm, lineno) { lineno = 0; while (getline 0) { lineno++; sub(/[ \t]*#.*$/, "", $0); if (NF == 0) continue; if ($1 == "name" || $1 == "node" || $1 == "nodename") nodename = $2; else if ($1 == "remote" || $1 == "peer" || $1 == "peername") peername = $2; else if ($1 == "server") ftpserver = $2; else if ($1 == "login") username = $2; else if ($1 == "password") { if (pw == "") password = $2; } else if ($1 == "syncmode" || $1 == "mode") { if (sm == "") syncmode = $2; } else if ($1 == "symsync" || $1 == "symmetric") symsync = 1; else if ($1 == "dir") rootdir = $2; else if ($1 == "dotfiles") { if (tolower($2) == "yes") includedots = 1; } else if ($1 == "allowblanks") { if (tolower($2) == "yes") allowblanks = 1; } else { printf ("%s: unknown config key: %s [%s:%s]\n", program, $1, filename, lineno); exit (1); } } close (filename); if (nodename == "" || peername == "") { printf ("%s: unset node and/or peername: %s\n", program, filename); exit (1); } if (ftpserver !~ /:/) ftpserver = ftpserver ":21"; return (0); } function nextarg(par, arg) { if (argi >= ARGC) { printf ("%s: missing argument: %s\n", program, par) >STDERR; exit (1); } arg = ARGV[argi]; ARGV[argi++] = ""; return (arg); } BEGIN { extension("/usr/local/lib/awk.file.so", "dlload"); STDERR = "/dev/stderr"; synctable["sync"] = "uu:nothing uc:get ud:remove ux:put" \ " cu:put cc:duplicate cd:put cx:put" \ " du:remove dc:get dd:ignore dx:ignore" \ " xu:get xc:get xd:ignore xx:ignore"; synctable["master"] = "uu:nothing uc:get ud:put ux:put" \ " cu:put cc:put cd:put cx:put" \ " du:remove dc:get dd:ignore dx:ignore" \ " xu:get xc:get xd:ignore xx:ignore"; synctable["slave"] = "uu:nothing uc:get ud:remove ux:put" \ " cu:put cc:get cd:put cx:put" \ " du:get dc:get dd:ignore dx:ignore" \ " xu:get xc:get xd:ignore xx:ignore"; synctable["mirror"] = "uu:nothing uc:get ud:remove ux:ignore" \ " cu:get cc:get cd:remove cx:ignore" \ " du:get dc:get dd:ignore dx:ignore" \ " xu:get xc:get xd:ignore xx:ignore"; synctable["original"] = "uu:nothing uc:put ud:put ux:put" \ " cu:put cc:put cd:put cx:put" \ " du:remove dc:remove dd:ignore dx:ignore" \ " xu:ignore xc:ignore xd:ignore xx:ignore"; program = "ftpsync"; argi = 1; debug = 0; conflictchar = ","; includedots = allowblanks = symsync = modeconfirm = 0; argi = 1; while (argi < ARGC && substr(ARGV[argi], 1, 1) == "-") { options = nextarg("option"); if (options == "--") break; for (i = 2; i<=length(options); i++) { c = substr(options, i, 1); if (c == "a") verbose = 1 else if (c == "d") debug = 1; else if (c == "l") listonly = 1; else if (c == "p") password = nextarg("password"); else if (c == "s") { modeset = 1; syncmode = nextarg("sync mode"); if (! (syncmode in synctable)) { printf ("%s: unknown sync mode: %s\n", program, syncmode); exit (1); } } else if (c == "y") modeconfirm = 1; else if (c == "z") symsync = 1; else { printf ("%s: unkown option: -%s\n", program, c) >STDERR; exit (1); } } } dir = nextarg("local directory"); if (chdir(dir) != 0) { printf ("%s: can't change to dir: %s, error= %s\n", program, dir, ERRNO) >STDERR; exit (1); } config = ".sync.conf"; if (argi < ARGC) config = ".sync." nextarg("configuration file"); readconfig(config, password, syncmode); nodesyncdata = sprintf (".sync-%s:%s", nodename, peername); peersyncdata = sprintf (".sync-%s:%s", peername, nodename); # # Set sync mode. # if (syncmode == "") syncmode = "sync"; if (! (syncmode in synctable)) { printf ("%s: unknown sync mode: %s\n", program, syncmode); exit (1); } n = split(synctable[syncmode], x, " "); for (i=1; i<=n; i++) { split(x[i], y, ":"); actiontab[y[1]] = y[2]; } if (modeset != 0 && modeconfirm == 0) { split("u c d x", x, " "); for (i=1; i<=4; i++) { s = x[i]; for (j=1; j<=4; j++) printf ("%c%s %-10s", s, x[j], actiontab[s x[j]]); printf ("\n"); } listonly = 1; printf ("\n"); } # # Read the server information first. I also prefer to fail at # the beginning if the server has a problem (or is not properly # configured). # server = doconnect(ftpserver, username, password); if (rootdir != "") docwd(server, rootdir); readserverinfo(server, ".", currentpeer); readsyncinfo(peersyncdata, prevpeer); computestatus(peerstatus, prevpeer, currentpeer); # # Read current directory contents. # dirlist[""] = sbuf[""] = ""; n = scandir(".", dirlist); for (i=1; i<=n; i++) { if (stat(dirlist[i], sbuf) != 0) dirlist[i] = ""; else if (sbuf["type"] != "file") continue; if (excludefile(dirlist[i])) continue; currentnode[dirlist[i]] = getnodeinfo(dirlist[i]); } readsyncinfo(nodesyncdata, prevnode); computestatus(nodestatus, prevnode, currentnode); # # We compute the `does not exist' status for files we do not # have on both ends. # for (file in nodestatus) { if (! (file in peerstatus)) peerstatus[file] = "x"; } for (file in peerstatus) { if (! (file in nodestatus)) nodestatus[file] = "x"; } # # Now we can compute the file's action for this sync run. # for (file in nodestatus) { status[file] = s = nodestatus[file] peerstatus[file]; action[file] = actiontab[s]; if (listonly == 1) printf ("%s %s %s\n", s, action[file], file); } if (listonly == 1) exit (0); # # Let's synchronize. # for (file in action) { s = action[file]; info = sprintf ("%s%c %s", status[file], toupper(substr(s, 1, 1)), file); if (s != "nothing" || verbose == 1) printf ("%s\n", info); if (s == "nothing" || s == "ignore") ; else if (s == "get") { doretr(server, "I", file, file); currentnode[file] = getnodeinfo(file); } else if (s == "put") { dostor(server, "I", file, file); currentpeer[file] = getpeerinfo(server, file); } else if (s == "duplicate") { dup = file conflictchar peername; doretr(server, "I", file, dup); currentnode[file] = getnodeinfo(file); cmd = sprintf ("cmp -s '%s' '%s'", file, dup); if (system(cmd) == 0) { printf ("= %s\n", file); unlink(dup); currentpeer[file] = getpeerinfo(server, file); } else { dup = file conflictchar nodename; dostor(server, "I", file, dup); currentpeer[file] = getpeerinfo(server, file); } } else if (s == "remove") { if (file in currentnode) { unlink(file); delete currentnode[file]; } if (file in currentpeer) { cfputc(server, "DELE", file, -250); delete currentpeer[file]; } } else { printf ("%s: undefined action: %s, file= %s\n", program, s, file); exit (1); } } writesyncinfo(nodesyncdata, currentnode); writesyncinfo(peersyncdata, currentpeer); if (symsync != 0) { dostor(server, "I", nodesyncdata, peersyncdata); dostor(server, "I", peersyncdata, nodesyncdata); } cfputc(server, "QUIT", "", 221); exit (0); }