home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / rxmir13.zip / rxMirror.cmd < prev    next >
OS/2 REXX Batch file  |  1999-02-21  |  50KB  |  1,318 lines

  1. /* rxMirror v1.3, an automated FTP mirroring tool: main REXX script
  2.  * Based on the work by Sergey Ayukov <asv@ayukov.com>
  3.  * Released as FREEWARE, see license.doc for details
  4.  */
  5. say "rxMirror v1.3 - an automated FTP mirroring tool in REXX"
  6. say "Copyright (C) Sergey Ayukov 1995,1996,1998 <asv@ayukov.com>."
  7. say "Copyright (C) Teemu Mannermaa 1998-1999 <wicked@clinet.fi>."
  8. say
  9.  
  10. /* Loading necessary DLL files */
  11. if rxFuncQuery("ftpLoadFuncs") then do
  12.     say "Loading rxFtp extensions..."
  13.     call rxFuncAdd "ftpLoadFuncs","rxFtp","ftpLoadFuncs"
  14.     if result \= "0" then do
  15.         say "Error loading rxFtp.dll"
  16.         exit 3
  17.     end
  18.     call ftpLoadFuncs "Quiet"
  19.     say
  20. end
  21. if rxFuncQuery("sysLoadFuncs") then do
  22.     say "Loading rexxUtil extensions..."
  23.     call rxFuncAdd "sysLoadFuncs","rexxUtil","sysLoadFuncs"
  24.     if result \= "0" then do
  25.         say "Error loading rexxUtil.dll"
  26.         exit 3
  27.     end
  28.     call sysLoadFuncs
  29.     say
  30. end
  31. if rxFuncQuery("rx2LoadFuncs") then do
  32.     say "Loading rx2Util extensions..."
  33.     call rxFuncAdd "rx2LoadFuncs","rx2Util","rx2LoadFuncs"
  34.     if result \= "0" then do
  35.         say "Error loading rx2Util.dll"
  36.         exit 3
  37.     end
  38.     call rx2LoadFuncs
  39.     say
  40. end
  41.  
  42. /* Set up configuration */
  43. glb. = ""                    /* Global data */
  44. cfg. = ""                    /* Configuration data */
  45. sts. = ""                    /* Status data */
  46. parse source . . glb.cmddir .
  47. glb.cmddir = filespec("drive", glb.cmddir)||tolower(filespec("path", glb.cmddir))
  48. glb.curdir = directory()'\'
  49. glb.optfile = glb.curdir".mirror.options"
  50. glb.crashdir = ""
  51. glb.maxtimlim = 59+(60*23)
  52. cfg.serverhost = ""
  53. cfg.startdir = ""
  54. cfg.localdir = glb.curdir
  55. cfg.user = "anonymous"
  56. cfg.passwd = ""
  57. cfg.loglvl = ""
  58. cfg.mainlog  = glb.curdir".mirror.mainlog"
  59. cfg.primarylog = glb.curdir".mirror.files"
  60. cfg.loclog = ".mirror.log"
  61. cfg.inlog  = ".mirror.in"
  62. cfg.outlog = ".mirror.out"
  63. cfg.scrlog = glb.curdir".mirror.scrlog"
  64. cfg.limit.kbytes = 25000
  65. cfg.limit.nfiles = 300
  66. cfg.limit.dirmins = -1
  67. cfg.limit.totmins = 60
  68. cfg.recovery = 0
  69. cfg.skip = ".mirror.skip"
  70. cfg.skipglb = glb.curdir".mirror.skip"
  71. cfg.inc = ".mirror.incl"
  72. cfg.incglb = glb.curdir".mirror.incl"
  73. cfg.reflect = ".mirror.reflect"
  74. cfg.ftpserver = 0
  75. cfg.trueremove = 0
  76. cfg.ignoretimestamp = 0
  77. cfg.savedirlist = ""
  78. cfg.relocate = ".mirror.relocate"
  79. cfg.linkedfiles = 0
  80. cfg.linklist = ".mirror.links"
  81. cfg.idlelim = -1
  82.  
  83. /* Handle options from file/command line */
  84. parse arg cmdarg
  85. if wordpos("-?", cmdarg) | wordpos("-H", translate(cmdarg)) \= 0 then do
  86.     call usage
  87.     exit 2
  88. end
  89. if stream(glb.optfile, 'c', "query exists") \= "" then do
  90.     say "Reading options from '"glb.optfile"'"
  91.     do while lines(glb.optfile) <> 0
  92.         lin = linein(glb.optfile)
  93.         if lin = "" | left(lin,1)=";" then iterate
  94.         lin = strip(lin)
  95.         say "Assigning option: "lin
  96.         interpret "cfg."lin
  97.     end
  98.     call stream glb.optfile, "C", "CLOSE"
  99.     say
  100. end
  101. call prcopt(cmdarg)
  102.  
  103. /* Remove screen output log, if it exists/is configured */
  104. if pos("S",cfg.loglvl) \= 0 then
  105.     if stream(cfg.scrlog, "c", "query exist") \= "" then
  106.         call sysFileDelete cfg.scrlog
  107.  
  108. /* Validate configuration */
  109. if cfg.linkedfiles < 0 & cfg.linkedfiles > 2 then do
  110.     call saylog "Invalid link processing option "cfg.linkedfiles", assuming 0"
  111.     cfg.linkedfiles = 0
  112. end
  113. if cfg.serverhost = "" then do
  114.     call saylog "Server to mirror is not defined"
  115.     call usage
  116.     exit 1
  117. end
  118. if cfg.startdir = "" then do
  119.     call saylog "Directory to mirror is not defined"
  120.     call usage
  121.     exit 1
  122. end
  123. if cfg.passwd = "" then do
  124.     call saylog "Password is not defined"
  125.     call usage
  126.     exit 1
  127. end
  128.  
  129. call saylog "Mirroring directory: "cfg.startdir
  130. call saylog "To local directory : "cfg.localdir
  131. call saylog "From server        : "cfg.serverhost
  132. call saylog "Userid             : "cfg.user
  133. call saylog "Password           : "cfg.passwd
  134. call saylog
  135.  
  136. if cfg.passwd = "*" then do
  137.     say "Enter your password for" cfg.user"@"cfg.serverhost
  138.     say "(will not appear when typed, empty line will abort):"
  139.     parse pull cfg.passwd
  140.     say ''
  141.     if cfg.passwd = "" then exit 1
  142. end
  143.  
  144. /* Initialize statistics */
  145. sts.fildl = 0
  146. sts.filrm = 0
  147. sts.bytdl = 0
  148. sts.bytrm = 0
  149.  
  150. /* Attempting crash recovery if necessary */
  151. signal off notready
  152. if cfg.recovery then do
  153.     do while lines(cfg.mainlog) <> 0
  154.         lin = linein(cfg.mainlog)
  155.         if substr(lin,22,3) = "dir" then do
  156.             completed = 0
  157.             crash = substr(lin,34)
  158.         end
  159.         else if substr(lin,22,3) = ">>>" then completed = 1
  160.     end
  161.     call stream cfg.recovery, "C", "CLOSE"
  162.     cfg.recovery = 0
  163.     if completed = 0 then do
  164.         call saylog "Crash condition detected; last directory is: "crash
  165.         glb.crashdir = crash
  166.         cfg.recovery = 1
  167.     end
  168. end
  169.  
  170. /* Logging into remote host */
  171. signal on halt name abort
  172. if pos("M", cfg.loglvl) \=0 then do
  173.     call logwrite cfg.mainlog "<<<<<<<<<--------------------------------------------"
  174.     call logwrite cfg.mainlog "host     :" cfg.serverhost
  175. end
  176.  
  177. call ftpSetUser cfg.serverhost, cfg.user, cfg.passwd
  178. if result \= 1 then call perror "Bad userid/password"
  179. i = 1
  180. do forever
  181.     call saylog "Trying to login to '"cfg.serverhost"', try #"i" ("1*i-1" minutes passed)"
  182.     call ftpPwd remotedir
  183.     if result = 0 then leave
  184.     if ftperrno = "FTPLOGIN" then do
  185.         if i > 5 then call perror "Login failed "i" times"
  186.         call saylog "Server is busy; waiting for 1 minutes"
  187.         call sysSleep 60*1
  188.         i = i + 1
  189.         iterate
  190.     end
  191.     call perror "Login failed"
  192. end
  193.  
  194. if cfg.limit.totmins > 0 then do
  195.     sts.tottimlim = time('M') + cfg.limit.totmins
  196.     /* *** ERROR *** */
  197.     /*if sts.tottimlim > glb.maxtimlim then sts.tottimlim = sts.tottimlim - glb.maxtimlim*/
  198. end
  199. call chkdir cfg.localdir
  200. call chgdir cfg.localdir
  201. call processdir cfg.startdir
  202. call chgdir glb.curdir
  203. if cfg.limit.totmins > 0 then
  204.     if time('M') > (sts.tottimlim-1) then do
  205.         call saylog "Total minute limit "cfg.limit.totmins" exceeded by "time('M')-sts.tottimlim" minutes, terminating"
  206.         if pos("M", cfg.loglvl) \=0 then
  207.             call logwrite cfg.mainlog "Total minute limit "cfg.limit.totmins" exceeded, terminating"
  208.     end
  209.  
  210. if pos("M", cfg.loglvl) \=0 then do
  211.     call logwrite cfg.mainlog "IN  " right(sts.fildl,5) "file(s)," right(nicenum(sts.bytdl),11) "bytes"
  212.     call logwrite cfg.mainlog "OUT " right(sts.filrm,5) "file(s)," right(nicenum(sts.bytrm),11) "bytes"
  213.     call logwrite cfg.mainlog ">>>>>>>>>--------------------------------------------"
  214. end
  215. call saylog
  216. call saylog "Totals:"
  217. call saylog "IN  " right(sts.fildl,5) "file(s)," right(nicenum(sts.bytdl),11) "bytes"
  218. call saylog "OUT " right(sts.filrm,5) "file(s)," right(nicenum(sts.bytrm),11) "bytes"
  219. call ftpLogoff
  220. exit 0
  221.  
  222. /* Performs mirroring for specified directory and subdirectories */
  223. processdir: procedure expose glb. cfg. sts. ftperrno
  224.     parse arg dir0
  225.  
  226. /* Set up current directory */
  227.     call saylog
  228.     call saylog "Changing remote directory to "dir0
  229.     call ftpChDir dir0
  230.     if result \= 0 then do
  231.         call saylog "*** WARNING: cannot chdir to "dir0
  232.         if pos("M", cfg.loglvl) \=0 then
  233.             call logwrite cfg.mainlog "!cannot chdir to '"dir0"'"
  234.         if ftperrno \= "FTPCOMMAND" then
  235.             call perror "Cannot do chdir to "dir0
  236.         return
  237.     end
  238.     call ftpPwd remotedir
  239.  
  240. /* Get remote directory listing */
  241.     call getinclist
  242.     call getskiplist
  243.     call getreloclist
  244.     call saylog "Getting remote file list"
  245.     call ftpDir ".", "file."
  246.     if result \= 0 then call perror "Error getting remote file list"
  247.     cyear = substr(date('S'), 1, 4)
  248.     cmonth = substr(date('S'), 5, 2)
  249.     drop remdir.
  250.     drop remfil.
  251.     drop remfilind.
  252.     remfil.num = 0
  253.     remfil.bytes = 0
  254.     remdir.num = 0
  255.     if cfg.savedirlist <> "" then call sysFileDelete cfg.savedirlist
  256.     call saylog "Processing remote file list with "file.0" entries"
  257.     if cfg.idlelim > 0 then call time 'R'
  258.     do i=1 to file.0
  259.         if (cfg.ftpserver = 0) | (cfg.ftpserver = 3) then do
  260.             parse value file.i with rflags . . . rlen rmonth rday rtime rname . rlink
  261.         end
  262.         if cfg.ftpserver = 1 then do
  263.             parse value file.i with rflags . . rlen rmonth rday rtime rname . rlink
  264.         end
  265.         if cfg.ftpserver = 2 then do
  266.             parse value file.i with rlen 19 . 30 rflags 36 rmonth'-'rday'-'ryear rtime rname
  267.             if strip(rflags)='' then rflags="-"
  268.         end
  269.         if cfg.savedirlist <> "" then call lineout cfg.savedirlist, file.i
  270.         rname = strip(rname)
  271.         if isdir(rlen rflags rname) then do
  272.             remdir.num = remdir.num + 1
  273.             tmp = remdir.num
  274.             remdir.tmp.dname = rname
  275.             remdir.tmp.len = rlen
  276.             remdir.tmp.flags = rflags
  277.         end
  278.         if (rworth(rlen rflags rname) | islink(rlen rflags rname rlink)) then do
  279.             remfil.bytes = remfil.bytes + rlen
  280.             remfil.num = remfil.num + 1
  281.             tmp = remfil.num
  282.             if (cfg.ftpserver = 0) | (cfg.ftpserver = 1) | (cfg.ftpserver = 3) then do
  283.                 rmonth = month2dec(rmonth)
  284.                 if pos(":", rtime) \= 0 then
  285.                     if rmonth > cmonth then ryear = cyear-1
  286.                     else                    ryear = cyear
  287.                 else do
  288.                     ryear = rtime
  289.                     rtime = "00:00"
  290.                 end
  291.             end
  292.             if ryear < 1900 then ryear = ryear + 1900
  293.             remfil.tmp.fname = rname
  294.             remfil.tmp.len  = rlen
  295.             remfil.tmp.flags = rflags
  296.             remfil.tmp.year  = ryear
  297.             remfil.tmp.month = rmonth
  298.             remfil.tmp.day   = rday
  299.             remfil.tmp.time  = rtime
  300.             call checkrelocate tmp
  301.             remfil.tmp.linkdir = ""
  302.             remfil.tmp.linkfil = ""
  303.             if islink(rlen rflags rname rlink) then call processlink tmp rlink
  304.             indkey=crtkey(remfil.tmp.fname)
  305.             remfilind.indkey=tmp
  306.         end
  307.         if sendnoop(time('E') i" of "file.0" entries processed")=0 then call time 'R'
  308.     end
  309.     if cfg.savedirlist <> "" then call stream cfg.savedirlist, "C", "CLOSE"
  310.     drop file.
  311.  
  312. /* Check error recovery conditions */
  313.     skipto = ""
  314.     if cfg.recovery then do
  315.         if left(glb.crashdir,length(dir0)) \= dir0 then
  316.             call perror "Illegal crash recovery attempt; "glb.crashdir dir0
  317.         skipto = substr(glb.crashdir,length(dir0)+2)
  318.         sp = pos("/",skipto)
  319.         if sp \= 0 then skipto = left(skipto,sp-1)
  320.         call saylog "Skipping to '"skipto"'"
  321.     end
  322.  
  323. /* Process this directory */
  324.     call dirmirror dir0
  325.  
  326. /* Process subdirs */
  327.     do i=1 to remdir.num
  328.         if cfg.limit.totmins > 0 then
  329.             if time('M') > (sts.tottimlim-1) then leave i
  330.         if \cfg.recovery then do
  331.             call chkdir remdir.i.dname
  332.             call chgdir remdir.i.dname
  333.             if result = "" then iterate i
  334.             call processdir dir0'/'remdir.i.dname
  335.             call chgdir ".."
  336.         end
  337.         else do
  338.             if remdir.i.dname = skipto then do
  339.                 call chkdir remdir.i.dname
  340.                 call chgdir remdir.i.dname
  341.                 if result = "" then iterate i
  342.                 call processdir dir0'/'remdir.i.dname
  343.                 call chgdir ".."
  344.             end
  345.         end
  346.     end i
  347.  
  348.     if sp = 0 then cfg.recovery = 0
  349. return
  350.  
  351. /* Mirroring of current dir on remote into current dir on local */
  352. dirmirror: procedure expose glb. ftperrno remfil. remfilind. cfg. sts. reloc.
  353.     parse arg dir
  354.  
  355.     call ftpSetBinary "Binary"
  356.     if pos("M", cfg.loglvl) \=0 then
  357.         call logwrite cfg.mainlog "directory: "dir
  358.  
  359.     call saylog right(remfil.num,5)" remote files   ("nicenum(remfil.bytes)" bytes)"
  360.     call getlocalfilelist  /* Build list of local files */
  361.  
  362. /* Check out if there's some file on remote and not on local disk */
  363.     call saylog "Checking/retrieving remote files, please wait..."
  364.     files_dl = 0
  365.     bytes_dl = 0
  366.     if cfg.limit.dirmins > 0 then do
  367.         curtimlim = time('M') + cfg.limit.dirmins
  368.         if curtimlim > glb.maxtimlim then curtimlim = curtimlim - glb.maxtimlim
  369.     end
  370.     if cfg.idlelim > 0 then call time 'R'
  371.     do i=1 to remfil.num
  372.         found=0
  373.         if locfil.num \= 0 then do
  374.             indkey=crtkey(remfil.i.locfname)
  375.             if symbol('locfilind.indkey') = "VAR" then do
  376.                 j=locfilind.indkey
  377.                 if match(i j) then found=1
  378.             end
  379.         end
  380.         if found=0 then do
  381.             if retrieve(i) = 0 then do
  382.                 files_dl = files_dl + 1
  383.                 bytes_dl = bytes_dl + remfil.i.len
  384.                 if pos("P", cfg.loglvl) \=0 then do
  385.                     if remfil.i.linkfil \= "" then linkspec = " -> "remfil.i.linkdir||remfil.i.linkfil
  386.                     else linkspec = ""
  387.                     call logwrite cfg.primarylog "+ "right(nicenum(remfil.i.len),11)" "dir"/"remfil.i.fname||linkspec
  388.                 end
  389.             end
  390.             if cfg.idlelim > 0 then call time 'R'
  391.         end
  392.         if cfg.limit.nfiles > 0 then
  393.             if files_dl >= cfg.limit.nfiles then do
  394.                 call saylog "File limit "cfg.limit.nfiles" reached, leaving dir"
  395.                 if pos("M", cfg.loglvl) \=0 then
  396.                     call logwrite cfg.mainlog "File limit "cfg.limit.nfiles" reached, leaving dir"
  397.                 leave i
  398.             end
  399.         if cfg.limit.kbytes > 0 then
  400.             if bytes_dl/1024 >= cfg.limit.kbytes then do
  401.                 call saylog "KByte limit "cfg.limit.kbytes" exceeded ("bytes_dl/1024"), leaving dir"
  402.                 if pos("M", cfg.loglvl) \=0 then
  403.                     call logwrite cfg.mainlog "KByte limit "cfg.limit.kbytes" exceeded ("bytes_dl/1024"), leaving dir"
  404.                 leave i
  405.             end
  406.         if cfg.limit.dirmins > 0 then
  407.             if time('M') > (curtimlim-1) then do
  408.                 call saylog "Minute limit "cfg.limit.dirmins" exceeded by "time('M')-curtimlim" minutes, leaving dir"
  409.                 if pos("M", cfg.loglvl) \=0 then
  410.                     call logwrite cfg.mainlog "Minute limit "cfg.limit.dirmins" exceeded, leaving dir"
  411.                 leave i
  412.             end
  413.         if cfg.limit.totmins > 0 then 
  414.             if time('M') > (sts.tottimlim-1) then do
  415.                 call saylog "Total minute limit "cfg.limit.totmins" exceeded by "time('M')-sts.tottimlim" minutes, leaving dir"
  416.                 if pos("M", cfg.loglvl) \=0 then
  417.                     call logwrite cfg.mainlog "Total minute limit "cfg.limit.totmins" exceeded, leaving dir"
  418.                 leave i
  419.             end
  420.         if found=1 then if sendnoop(time('E') i" of "remfil.num" files checked")=0 then call time 'R'
  421.     end i
  422.  
  423.     if files_dl \= 0 | bytes_dl \= 0 then do
  424.         if pos("L", cfg.loglvl) \=0 then
  425.             call logwrite cfg.loclog "IN  " right(files_dl,5)" file(s), "right(nicenum(bytes_dl),11)" bytes"
  426.         call saylog "Retrieved "files_dl" file(s), "nicenum(bytes_dl)" bytes"
  427.         call getlocalfilelist /* Rebuild local list to accomodate changes */
  428.         sts.fildl = sts.fildl + files_dl
  429.         sts.bytdl = sts.bytdl + bytes_dl
  430.     end
  431.  
  432. /* Check out if there's some file on local disk and not on remote */
  433.     call saylog "Checking/removing local files, please wait..."
  434.     files_rm = 0
  435.     bytes_rm = 0
  436.     remdircheck = 0
  437.     if cfg.idlelim > 0 then call time 'R'
  438.     do j=1 to locfil.curdnum + locfil.relcnum
  439.         found=0
  440.         indkey=crtkey(locfil.j.remfname)
  441.         if symbol('remfilind.indkey') = "VAR" then do
  442.             i=remfilind.indkey
  443.             if translate(strip(remfil.i.locfname,"T","."))=translate(locfil.j.fname) then
  444.                 found=1
  445.         end
  446.         if found=0 then do
  447.             if \cfg.trueremove then do
  448.                 if \remdircheck then call chkdir ".removed"
  449.                 call saylog "Removing local file '"locfil.j.locdir""locfil.j.fname"' ("locfil.j.len" bytes) as obsolete"
  450.                 call sysFileDelete '.removed\'locfil.j.fname
  451.                 '@move' '"'locfil.j.locdir||locfil.j.fname'" ".removed\'locfil.j.fname'" >nul'
  452.                 remdircheck = 1
  453.             end
  454.             else call sysFileDelete locfil.j.locdir||locfil.j.fname
  455.             if pos("F", cfg.loglvl) \=0 then
  456.                 call logwrite cfg.outlog right(locfil.j.len,10) locfil.j.locdir""locfil.j.fname
  457.             if pos("P", cfg.loglvl) \=0 then
  458.                 call logwrite cfg.primarylog "- "right(nicenum(locfil.j.len),11)" "dir"/"locfil.j.remfname
  459.             files_rm = files_rm + 1
  460.             bytes_rm = bytes_rm + locfil.j.len
  461.         end
  462.         if found=1 then if sendnoop(time('E') j" of "locfil.curdnum+locfil.relcnum" files checked")=0 then call time 'R'
  463.     end j
  464.  
  465.     if files_rm \= 0 | bytes_rm \= 0 then do
  466.         if pos("L", cfg.loglvl) \=0 then
  467.             call logwrite cfg.loclog "OUT " right(files_rm,5)" file(s), "right(nicenum(bytes_rm),11)" bytes"
  468.         call saylog "Removed "files_rm" file(s), "nicenum(bytes_rm)" bytes"
  469.         sts.filrm = sts.filrm + files_rm
  470.         sts.bytrm = sts.bytrm + bytes_rm
  471.     end
  472. return
  473.  
  474. /* Send NOOP to make some noice */
  475. sendnoop: procedure expose glb. cfg.
  476.     parse arg curtim msg
  477.     retcde = -1
  478.     if cfg.idlelim<0 then return retcde
  479.     if (cfg.idlelim=0) | (trunc(curtim)>=cfg.idlelim) then do
  480.         call saylog "Sending NOOP, "msg
  481.         call ftpQuote "noop"        /* To make sure we stay online */
  482.         if cfg.idlelim > 0 then retcde=0
  483.     end
  484. return retcde
  485.  
  486. /* Determine whether remote file worth to mirror */
  487. rworth: procedure expose glb. skp. inc.
  488.     parse arg len flags fname
  489.     if fname = ""                then return 0
  490.     if left(fname,1)  = "."      then return 0
  491.     if left(flags,1) \= "-"      then return 0
  492.     if length(fname) < 1         then return 0
  493.     if words(flags) > 1          then return 0
  494.     if pos(".bad",fname) \= 0    then return 0
  495.     if pos(".core",fname) \= 0   then return 0
  496.     if pos(".try",fname) \= 0    then return 0
  497.     if fname = "descript.ion"    then return 0
  498.     if \isvalid(fname)           then return 0
  499.     found = 0
  500.     do i = 1 to inc.num
  501.         if wildcardmatch(inc.i fname) then do
  502.             found = 1
  503.             leave
  504.         end
  505.     end
  506.     if \found then return 0
  507.     do i = 1 to skp.num
  508.         if wildcardmatch(skp.i fname) then return 0
  509.     end
  510. return 1
  511.  
  512. /* Determine whether local file worth to mirror */
  513. lworth: procedure expose glb.
  514.     parse arg fname len
  515.     if left(fname,1)  = "."    then return 0
  516.     if length(fname) < 1       then return 0
  517.     if \isvalid(fname)         then return 0
  518.     if fname = "descript.ion"  then return 0
  519. return 1
  520.  
  521. /* Do two files match? */
  522. match: procedure expose glb. remfil. locfil. cfg.
  523.     parse arg i j
  524.     if translate(strip(remfil.i.locfname,"T",".")) \= translate(locfil.j.fname) then return 0
  525.     if remfil.i.len \= locfil.j.len  then return 0
  526.     if cfg.ignoretimestamp then return 1
  527.     /*say "remote ["remotetimestamp(i)"] local ["locfil.j.date"] ("substr(remotetimestamp(i),8)")"
  528.     say "->"left(remotetimestamp(i),7)"<- ->"left(locfil.j.date,7)"<-"*/
  529.     if substr(remotetimestamp(i), 8) = "00:00" then do
  530.         if left(remotetimestamp(i), 7) \= left(locfil.j.date, 7) then return 0
  531.     end
  532.     else if remotetimestamp(i) \= locfil.j.date then return 0
  533. return 1
  534.  
  535. /* Fetching file from remote */
  536. retrieve: procedure expose glb. cfg. remfil.
  537.     parse arg index
  538.     filename = remfil.index.fname
  539.     tofilnam = remfil.index.locdir||remfil.index.locfname
  540.     call saylog "Retrieving file '"filename"' to '"tofilnam"' ("nicenum(remfil.index.len)" bytes)"
  541.     elapsedtime = 0.000000
  542.     linkspec = ""
  543.     if remfil.index.linkfil \= "" then do
  544.         linktodir = remfil.index.linkdir
  545.         linktofil = remfil.index.linkfil
  546.         call saylog "  as a link to local file '"linktodir||linktofil"'"
  547.         linkspec = " -> "||linktodir||linktofil
  548.         call sysFileDelete tofilnam
  549.         call stream tofilnam, "C", "OPEN"
  550.         call stream tofilnam, "C", "CLOSE"
  551.         if cfg.linklist \= "" then do
  552.             call lineout cfg.linklist, tofilnam||' 'linktodir||linktofil
  553.             call stream cfg.linklist, "C", "CLOSE"
  554.         end
  555.     end
  556.     else do
  557.         call time 'R'
  558.         call ftpGet tofilnam, filename
  559.         elapsedtime = time('E')
  560.         if result \= 0 then do
  561.             call saylog "*** WARNING: Unable to download file '"filename"'; ftperrno is" ftperrno
  562.             if pos("M", cfg.loglvl) \=0 then
  563.                 call logwrite cfg.mainlog "!Unable to download '"filename"'"
  564.             if ftperrno \= "FTPCOMMAND" then
  565.                 call perror "File fetching failed on "filename
  566.             ftperrno = ""
  567.             return 1
  568.         end
  569.     end
  570.     if pos("F", cfg.loglvl) \=0 then
  571.         call logwrite cfg.inlog right(remfil.index.len,8)' 'right(calccps(elapsedtime remfil.index.len),6)' cps 'right(nicedltime(elapsedtime),11)' 'remfil.index.locdir||remfil.index.locfname||linkspec
  572.     if length(remfil.index.time) = 4 then do
  573.         remfil.index.time = "0"||remfil.index.time
  574.     end
  575.     hour = substr(remfil.index.time, 1, 2)
  576.     min = substr(remfil.index.time, 4, 2)
  577.     call rx2SetFileTimestamp tofilnam, remfil.index.day, remfil.index.month, remfil.index.year, hour, min, 0
  578.     if result \= 0 then call saylog "  Unable to set timestamp to "remfil.index.day"."remfil.index.month"."remfil.index.year" "hour":"min":00 (rc is "result")"
  579.     call saylog '  'right(nicedltime(elapsedtime),11)' (hh:mm:ss.ms), 'right(calccps(elapsedtime remfil.index.len),6)' cps'
  580. return 0
  581.  
  582. /* Write a string to log file */
  583. logwrite: procedure expose glb.
  584.     parse arg logname str
  585.     call stream  logname, "C", "OPEN WRITE"
  586.     call stream  logname, "C", "SEEK <0"
  587.     call lineout logname, right(date(),11) time()' 'str
  588.     call stream  logname, "C", "CLOSE"
  589. return 1
  590.  
  591. /* Process errors */
  592. perror: procedure expose glb. cfg. ftperrno
  593.     parse arg errstr
  594.     call saylog errstr", ftp error code is" ftperrno
  595.     if pos("M", cfg.loglvl) \=0 then
  596.         call logwrite cfg.mainlog "ERR: "errstr", ftp error code is "ftperrno
  597.     call chgdir glb.curdir
  598.     exit 1
  599.  
  600. /* Write a string to log file and to screen */
  601. saylog: procedure expose glb. cfg.
  602.     parse arg str
  603.     say str
  604.     if pos("S", cfg.loglvl) \=0 then
  605.         call logwrite cfg.scrlog str
  606. return 1
  607.  
  608. /* Nice digit output */
  609. nicenum: procedure expose glb.
  610.     parse arg num
  611.     bil = num%1000000000
  612.     mil = num%1000000 - bil*1000
  613.     th  = num%1000 - mil*1000 - bil*1000000
  614.     ed  = num - th*1000 - mil*1000000 - bil*1000000000
  615.     out = ""
  616.  
  617.     if bil \= 0 then out = out""bil" "
  618.  
  619.     if mil \= 0 then
  620.         if bil \= 0 then out = out""right(mil,3,'0')" "
  621.         else             out = out""mil" "
  622.     else
  623.         if bil \= 0 then out = out""right(mil,3,'0')" "
  624.  
  625.     if th  \= 0 then
  626.         if bil \= 0 | mil \= 0 then out = out""right(th,3,'0')" "
  627.         else                        out = out""th" "
  628.     else
  629.         if bil \= 0 | mil \= 0 then out = out""right(th,3,'0')" "
  630.  
  631.     if bil \= 0 | mil \= 0 | th \= 0 then out = out""right(ed,3,'0')
  632.     else                                  out = out""ed
  633. return out
  634.  
  635. /* Format nice download time */
  636. nicedltime: procedure expose glb.
  637.     parse arg dltime .
  638.     msec = substr(dltime,pos(".",dltime)+1,2)
  639.     secs = trunc(dltime)
  640.     mins = 0
  641.     hours = 0
  642.     if secs > 59 then do
  643.         mins = secs%60
  644.         secs = secs-mins*60
  645.     end
  646.     if mins > 59 then do
  647.         hours = mins%60
  648.         mins = mins-hours*60
  649.     end
  650.     retvar = secs||"."||msec
  651.     if mins > 0 then do
  652.         if length(retvar) < 5 then retvar = "0"retvar
  653.         retvar = mins||":"||retvar
  654.     end
  655.     if hours > 0 then do
  656.         if length(retvar) < 8 then retvar = "0"retvar
  657.         retvar = hours||":"||retvar
  658.     end
  659. return retvar
  660.  
  661. /* Calculate CPS */
  662. calccps: procedure expose glb.
  663.     parse arg dltime dllen .
  664.     if dltime = 0 then retvar = 0
  665.     else retvar = trunc(dllen/dltime)
  666. return retvar
  667.  
  668. /* User abort */
  669. abort:
  670.     if pos("M", cfg.loglvl) \=0 then do
  671.         call saylog "User abort detected, terminating"
  672.         call logwrite cfg.mainlog "ERR: User abort detected"
  673.     end
  674.     call chgdir glb.curdir
  675.     call ftpLogoff
  676.     exit 1
  677.  
  678. /* Get local file list (including reflections and relocation) */
  679. getlocalfilelist: procedure expose glb. locfil. locfilind. cfg. reloc.
  680.     drop locfil.
  681.     drop locfilind.
  682.     locfil.num = 0
  683.     locfil.bytes = 0
  684.     call getcurdfilelist
  685.     call getrelocations
  686.     call getreflections
  687.     call saylog right(locfil.num,5)" local files    ("nicenum(locfil.bytes)" bytes)"
  688.     if locfil.curdnum > 0 then
  689.         call saylog right(locfil.curdnum,5)" in current dir ("nicenum(locfil.curdbytes)" bytes)"
  690.     if locfil.reflnum > 0 then
  691.         call saylog right(locfil.reflnum,5)" reflected      ("nicenum(locfil.reflbytes)" bytes)"
  692.     if locfil.relcnum > 0 then
  693.         call saylog right(locfil.relcnum,5)" relocated      ("nicenum(locfil.relcbytes)" bytes)"
  694. return
  695.  
  696. /* Getting file list for current directory */
  697. getcurdfilelist: procedure expose glb. locfil. locfilind. reloc.
  698.     locfil.curdnum = 0
  699.     locfil.curdbytes = 0
  700.     cutpos = length(directory())+2
  701.     call sysFileTree "*", "file.", "F"
  702.     do i=1 to file.0
  703.         parse value file.i with dat tim llen . lname
  704.         lname = substr(strip(lname), cutpos)
  705.         if lworth(lname llen) then do
  706.             locfil.curdnum = locfil.curdnum + 1
  707.             locfil.curdbytes = locfil.curdbytes + llen
  708.             locfil.num = locfil.num + 1
  709.             locfil.bytes = locfil.bytes + llen
  710.             tmp = locfil.num
  711.             locfil.tmp.fname = lname
  712.             locfil.tmp.len = llen
  713.             locfil.tmp.date = maketimestamp(dat tim)
  714.             locfil.tmp.locdir = ""
  715.             locfil.tmp.remfname = lname
  716.             indkey=crtkey(locfil.tmp.fname)
  717.             locfilind.indkey=tmp
  718.  
  719.             /* Check for rename */
  720.             do r = 1 to reloc.0
  721.                 do n = 1 to reloc.r.inc.0
  722.                     if wildcardmatch(reloc.r.inc.n locfil.tmp.fname) then do
  723.                         do s = 1 to reloc.r.skp.0
  724.                             if wildcardmatch(reloc.r.skp.s locfil.tmp.fname) then iterate
  725.                         end
  726.                         if reloc.r.cnvproc \= "" then do
  727.                             reloc.r.cnvproc locfil.tmp.fname "unpack"
  728.                             if queued() \= 0 then parse pull locfil.tmp.remfname
  729.                         end
  730.                     end
  731.                 end
  732.             end
  733.         end
  734.     end
  735.     drop file.
  736. return
  737.  
  738. /* Getting reflections list */
  739. getreflections: procedure expose glb. locfil. locfilind. cfg.
  740.     locfil.reflnum = 0
  741.     locfil.reflbytes = 0
  742.     do while lines(cfg.reflect) <> 0
  743.         lin = linein(cfg.reflect)
  744.         if lin = "" | left(lin,1) = ";" then iterate
  745.         lin = strip(strip(lin),"T","\")
  746.         call saylog "Reflecting "lin
  747.         call sysFileTree lin"\*", "file.", "F"
  748.         do i=1 to file.0
  749.             parse value file.i with dat tim llen . lname
  750.             lname = filespec("NAME", lname)
  751.             if lworth(lname llen) then do
  752.                 locfil.reflnum = locfil.reflnum + 1
  753.                 locfil.reflbytes = locfil.reflbytes + llen
  754.                 locfil.num = locfil.num + 1
  755.                 locfil.bytes = locfil.bytes + llen
  756.                 tmp = locfil.num
  757.                 locfil.tmp.fname = lname
  758.                 locfil.tmp.len = llen
  759.                 locfil.tmp.date = maketimestamp(dat tim)
  760.                 locfil.tmp.locdir = lin"\"
  761.                 locfil.tmp.remfname = lname
  762.                 indkey=crtkey(locfil.tmp.fname)
  763.                 locfilind.indkey=tmp
  764.             end
  765.         end
  766.         drop file.
  767.     end
  768.     call stream cfg.reflect, "C", "CLOSE"
  769. return
  770.  
  771. /* Getting relocations list */
  772. getrelocations: procedure expose glb. locfil. locfilind. cfg. reloc.
  773.     locfil.relcnum = 0
  774.     locfil.relcbytes = 0
  775.     do r = 1 to reloc.0
  776.         if reloc.r.locdir = "" then iterate
  777.         call sysFileTree reloc.r.locdir"*", "file.", "F"
  778.         do i=1 to file.0
  779.             parse value file.i with dat tim llen . lname
  780.             lname = filespec("NAME", lname)
  781.             if lworth(lname llen) then do
  782.                 locfil.relcnum = locfil.relcnum + 1
  783.                 locfil.relcbytes = locfil.relcbytes + llen
  784.                 locfil.num = locfil.num + 1
  785.                 locfil.bytes = locfil.bytes + llen
  786.                 tmp = locfil.num
  787.                 locfil.tmp.fname = lname
  788.                 locfil.tmp.len = llen
  789.                 locfil.tmp.date = maketimestamp(dat tim)
  790.                 locfil.tmp.locdir = reloc.r.locdir
  791.                 locfil.tmp.remfname = lname
  792.                 if reloc.r.cnvproc \= "" then do
  793.                     reloc.r.cnvproc lname "unpack"
  794.                     if queued() \= 0 then  parse pull locfil.tmp.remfname
  795.                 end
  796.                 indkey=crtkey(locfil.tmp.fname)
  797.                 locfilind.indkey=tmp
  798.             end
  799.         end
  800.         drop file.
  801.     end
  802. return
  803.  
  804. /* Getting skiplist */
  805. getskiplist: procedure expose glb. cfg. skp.
  806.     tmpnum = 0
  807.     do while lines(cfg.skipglb) <> 0
  808.         lin = linein(cfg.skipglb)
  809.         if lin = "" | left(lin,1)=";" then iterate
  810.         lin = strip(strip(lin),"T","\")
  811.         tmpnum = tmpnum + 1
  812.         skp.tmpnum = lin
  813.         call saylog "Added global skip to skiplist: "skp.tmpnum
  814.     end
  815.     call stream cfg.skipglb, "C", "CLOSE"
  816.     do while lines(cfg.skip) <> 0
  817.         lin = linein(cfg.skip)
  818.         if lin = "" | left(lin,1) = ";" then iterate
  819.         lin = strip(strip(lin),"T","\")
  820.         tmpnum = tmpnum + 1
  821.         skp.tmpnum = lin
  822.         call saylog "Added local skip to skiplist: "skp.tmpnum
  823.     end
  824.     call stream cfg.skip, "C", "CLOSE"
  825.     skp.num = tmpnum
  826. return
  827.  
  828. /* Getting include list */
  829. getinclist: procedure expose glb. cfg. inc.
  830.     tmpnum = 0
  831.     do while lines(cfg.incglb) <> 0
  832.         lin = linein(cfg.incglb)
  833.         if lin = "" | left(lin,1)=";" then iterate
  834.         lin = strip(strip(lin),"T","\")
  835.         tmpnum = tmpnum + 1
  836.         inc.tmpnum = lin
  837.         call saylog "Added global include: "inc.tmpnum
  838.     end
  839.     call stream cfg.incglb, "C", "CLOSE"
  840.     do while lines(cfg.inc) <> 0
  841.         lin = linein(cfg.inc)
  842.         if lin = "" | left(lin,1) = ";" then iterate
  843.         lin = strip(strip(lin),"T","\")
  844.         tmpnum = tmpnum + 1
  845.         inc.tmpnum = lin
  846.         call saylog "Added local include: "inc.tmpnum
  847.     end
  848.     call stream cfg.inc, "C", "CLOSE"
  849.     inc.num = tmpnum
  850.     if inc.num = 0 then do /* Default is to include everything */
  851.         inc.num = 1
  852.         inc.1 = "*"
  853.     end
  854. return
  855.  
  856. /* Getting relocation list */
  857. getreloclist: procedure expose glb. cfg. reloc.
  858.     drop reloc.
  859.     tmpnum = 0
  860.     do while lines(cfg.relocate) <> 0
  861.         lin = linein(cfg.relocate)
  862.         if lin = "" | left(lin,1) = ";" then iterate
  863.         parse var lin todir conv inclist skplist .
  864.         todir = strip(strip(todir),"T","\")
  865.         tmpnum = tmpnum + 1
  866.         if todir \= "." then reloc.tmpnum.locdir = todir"\"
  867.         else reloc.tmpnum.locdir = ""
  868.         if (conv = "") | (translate(conv) = "*NONE") then reloc.tmpnum.cnvproc=""
  869.         else interpret "reloc.tmpnum.cnvproc = "conv
  870.         call saylog "Adding relocation to: "reloc.tmpnum.locdir
  871.         if reloc.tmpnum.cnvproc \= "" then
  872.             call saylog "  with rename proc: "reloc.tmpnum.cnvproc
  873.  
  874.         incnum = 0
  875.         if inclist = "" then inclist = reloc.tmpnum.locdir||cfg.inc
  876.         call saylog "  with include list: "inclist
  877.         do while lines(inclist) <> 0
  878.             lin = linein(inclist)
  879.             if lin = "" | left(lin,1) = ";" then iterate
  880.             lin = strip(strip(lin),"T","\")
  881.             incnum = incnum + 1
  882.             reloc.tmpnum.inc.incnum = lin
  883.             call saylog "  added relocation include: "reloc.tmpnum.inc.incnum
  884.         end
  885.         call stream inclist, "C", "CLOSE"
  886.         reloc.tmpnum.inc.0 = incnum
  887.  
  888.         skpnum = 0
  889.         if skplist = "" then skplist = reloc.tmpnum.locdir||cfg.skip
  890.         call saylog "  with skip list: "skplist
  891.         do while lines(skplist) <> 0
  892.             lin = linein(skplist)
  893.             if lin = "" | left(lin,1) = ";" then iterate
  894.             lin = strip(strip(lin),"T","\")
  895.             skpnum = skpnum + 1
  896.             reloc.tmpnum.skp.skpnum = lin
  897.             call saylog "  added relocation skip: "reloc.tmpnum.skp.skpnum
  898.         end
  899.         call stream skplist, "C", "CLOSE"
  900.         reloc.tmpnum.skp.0 = skpnum
  901.     end
  902.     call stream cfg.relocate, "C", "CLOSE"
  903.     reloc.0 = tmpnum
  904. return
  905.  
  906. /* Getting relocation list for link target */
  907. getlinkreloclist: procedure expose glb. cfg. remfil. linkreloc.
  908.     parse arg index
  909.     tmpnum = 0
  910.     do while lines(remfil.index.linkdir||cfg.relocate) <> 0
  911.         lin = linein(remfil.index.linkdir||cfg.relocate)
  912.         if lin = "" | left(lin,1) = ";" then iterate
  913.         parse var lin todir conv inclist skplist .
  914.         todir = strip(strip(todir),"T","\")
  915.         tmpnum = tmpnum + 1
  916.         if todir\="." then linkreloc.tmpnum.locdir=remfil.index.linkdir||todir"\"
  917.         else linkreloc.tmpnum.locdir = remfil.index.linkdir
  918.         if (conv = "")|(translate(conv)="*NONE") then linkreloc.tmpnum.cnvproc=""
  919.         else interpret "linkreloc.tmpnum.cnvproc = "conv
  920.         call saylog "Found link target relocation to: "linkreloc.tmpnum.locdir
  921.         if linkreloc.tmpnum.cnvproc \= "" then
  922.             call saylog "  with rename proc: "linkreloc.tmpnum.cnvproc
  923.  
  924.         incnum = 0
  925.         if inclist = "" then inclist = linkreloc.tmpnum.locdir||cfg.inc
  926.         else inclist = remfil.index.linkdir||inclist
  927.         call saylog "  with include list: "inclist
  928.         do while lines(inclist) <> 0
  929.             lin = linein(inclist)
  930.             if lin = "" | left(lin,1) = ";" then iterate
  931.             lin = strip(strip(lin),"T","\")
  932.             incnum = incnum + 1
  933.             linkreloc.tmpnum.inc.incnum = lin
  934.             call saylog "  added relocation include: "linkreloc.tmpnum.inc.incnum
  935.         end
  936.         call stream inclist, "C", "CLOSE"
  937.         linkreloc.tmpnum.inc.0 = incnum
  938.  
  939.         skpnum = 0
  940.         if skplist = "" then skplist = linkreloc.tmpnum.locdir||cfg.skip
  941.         else skplist = remfil.index.linkdir||skplist
  942.         call saylog "  with skip list: "skplist
  943.         do while lines(skplist) <> 0
  944.             lin = linein(skplist)
  945.             if lin = "" | left(lin,1) = ";" then iterate
  946.             lin = strip(strip(lin),"T","\")
  947.             skpnum = skpnum + 1
  948.             linkreloc.tmpnum.skp.skpnum = lin
  949.             call saylog "  added relocation skip: "linkreloc.tmpnum.skp.skpnum
  950.         end
  951.         call stream skplist, "C", "CLOSE"
  952.         linkreloc.tmpnum.skp.0 = skpnum
  953.     end
  954.     call stream remfil.index.linkdir||cfg.relocate, "C", "CLOSE"
  955.     linkreloc.0 = tmpnum
  956. return
  957.  
  958. /* Check is there relocation for this file */
  959. checkrelocate: procedure expose glb. remfil. reloc.
  960.     parse arg index
  961.     remfil.index.locdir = ""
  962.     remfil.index.locfname = remfil.index.fname
  963.     do r = 1 to reloc.0
  964.         do i = 1 to reloc.r.inc.0
  965.             if wildcardmatch(reloc.r.inc.i remfil.index.fname) then do
  966.                 do s = 1 to reloc.r.skp.0
  967.                     if wildcardmatch(reloc.r.skp.s remfil.index.fname) then return
  968.                 end
  969.                 remfil.index.locdir = reloc.r.locdir
  970.                 if reloc.r.cnvproc \= "" then do
  971.                     reloc.r.cnvproc remfil.index.fname "pack"
  972.                     if queued() \= 0 then parse pull remfil.index.locfname
  973.                 end
  974.             end
  975.         end
  976.     end
  977. return
  978.  
  979. /* Check for link target relocation */
  980. checklinkreloc: procedure expose glb. cfg. remfil.
  981.     parse arg index
  982.     call getlinkreloclist index
  983.     do r = 1 to linkreloc.0
  984.         do i = 1 to linkreloc.r.inc.0
  985.             if wildcardmatch(linkreloc.r.inc.i remfil.index.linkfil) then do
  986.                 do s = 1 to linkreloc.r.skp.0
  987.                     if wildcardmatch(linkreloc.r.skp.s remfil.index.linkfil) then return
  988.                 end
  989.                 remfil.index.linkdir = linkreloc.r.locdir
  990.                 if linkreloc.r.cnvproc \= "" then do
  991.                     linkreloc.r.cnvproc remfil.index.linkfil "pack"
  992.                     if queued() \= 0 then parse pull remfil.index.linkfil
  993.                 end
  994.             end
  995.         end
  996.     end
  997. return
  998.  
  999. /* Change directory */
  1000. chgdir: procedure expose glb.
  1001.     parse arg dirname
  1002.     realdir = translate(dirname,'\','/')
  1003.     if right(realdir,1) = '\' then realdir=substr(realdir,1,length(realdir)-1)
  1004.     call directory realdir
  1005. return
  1006.  
  1007. /* Checking dir presence */
  1008. chkdir: procedure expose glb.
  1009.     parse arg chkdir
  1010.     dirname = translate(chkdir,'\','/')
  1011.     if right(dirname,1) = '\' then dirname=substr(dirname,1,length(dirname)-1)
  1012.     call sysFileTree dirname, "dir.", "D"
  1013.     if dir.0 = 0 then call sysMkDir dirname
  1014. return
  1015.  
  1016. /* Process linked remote files */
  1017. processlink: procedure expose glb. remfil. cfg.
  1018.     parse arg fnum rlink .
  1019.     call saylog
  1020.     call saylog "Remote file '"remfil.fnum.fname"' is a link to '"rlink"'"
  1021.     if cfg.linkedfiles = 0 then return /* Just to be sure */
  1022.     if cfg.linkedfiles = 2 then do
  1023.         locdir = translate(rlink,'\','/')
  1024.         lpos = lastpos('\', locdir)
  1025.         locfil = substr(locdir,lpos+1)
  1026.         locdir = substr(locdir,1,lpos)
  1027.         remfil.fnum.linkdir = locdir
  1028.         remfil.fnum.linkfil = locfil
  1029.         call checklinkreloc fnum
  1030.         call saylog "Checking local file "remfil.fnum.linkfil" at directory "remfil.fnum.linkdir
  1031.         call sysFileTree remfil.fnum.linkdir||remfil.fnum.linkfil, "file.", "F"
  1032.         if file.0 = 1 then do
  1033.             parse value file.1 with ldat ltim . . lname
  1034.             locdir = strip(translate(lname,'\','/'))
  1035.             lpos = lastpos('\', locdir)
  1036.             locdir = substr(locdir,1,lpos)
  1037.             remfil.fnum.linkdir = locdir
  1038.             remfil.bytes = remfil.bytes - remfil.fnum.len
  1039.             remfil.fnum.len = 0
  1040.  
  1041.             parse value ldat with rmonth'/'rday'/'ryear
  1042.             parse value ltim with rhour':'rmin
  1043.             if right(rmin,1) = "p" then rhour = rhour + 12
  1044.             if rhour = 24 then rhour = 12
  1045.             if length(rhour) < 2 then rhour = "0"rhour
  1046.             rmin = substr(rmin,1,2)
  1047.             remfil.fnum.year  = "19"||ryear
  1048.             remfil.fnum.month = rmonth
  1049.             remfil.fnum.day   = rday
  1050.             remfil.fnum.time  = rhour":"rmin
  1051.  
  1052.             call saylog "Local file found in directory '"remfil.fnum.linkdir"' ("remfil.fnum.day"."remfil.fnum.month"."remfil.fnum.year" "remfil.fnum.time")"
  1053.             call saylog
  1054.             return
  1055.         end
  1056.         call saylog "Local file not found, transfering link as normal file"
  1057.         remfil.fnum.linkdir = ""
  1058.         remfil.fnum.linkfil = ""
  1059.     end
  1060.     call ftpDir rlink, "file."
  1061.     if file.0 \= 1 then do
  1062.         call saylog "Failed to get link info, "file.0" remote files found"
  1063.         remfil.bytes = remfil.bytes - remfil.fnum.len
  1064.         remfil.num = remfil.num - 1
  1065.         return
  1066.     end
  1067.     if cfg.ftpserver = 0 then parse value file.1 with . . . . lnklen .
  1068.     else parse value file.1 with . . . lnklen .
  1069.     remfil.bytes = remfil.bytes - remfil.fnum.len + lnklen
  1070.     remfil.fnum.len = lnklen
  1071.     call saylog
  1072. return
  1073.  
  1074. /* Is this a link or not, 0 = no; 1 = yes */
  1075. islink: procedure expose glb. skp. inc. cfg.
  1076.     parse arg len flags fname link
  1077.     if cfg.linkedfiles = 0       then return 0
  1078.     if fname = ""                then return 0
  1079.     if fname = "descript.ion"    then return 0
  1080.     if left(fname,1)  = "."      then return 0
  1081.     if left(flags,1) \= "l"      then return 0
  1082.     if length(fname) < 1         then return 0
  1083.     if words(flags) > 1          then return 0
  1084.     if pos(".bad",fname) \= 0    then return 0
  1085.     if pos(".core",fname) \= 0   then return 0
  1086.     if pos(".try",fname) \= 0    then return 0
  1087.     if \isvalid(fname)           then return 0
  1088.     if link = ""                 then return 0
  1089.     found = 0
  1090.     do i = 1 to inc.num
  1091.         if wildcardmatch(inc.i fname) then do
  1092.             found = 1
  1093.             leave
  1094.         end
  1095.     end
  1096.     if \found then return 0
  1097.     do i = 1 to skp.num
  1098.         if wildcardmatch(skp.i fname) then return 0
  1099.     end
  1100. return 1
  1101.  
  1102. /* Determine is this directory or not */
  1103. isdir: procedure expose glb. skp. inc. cfg.
  1104.     parse arg len flags dname
  1105.     if cfg.ftpserver = 2 then do /* OS/2 servers */
  1106.         if word(flags,1) = "DIR" then return 1
  1107.         else                          return 0
  1108.     end
  1109.     if flags = ""                then return 0
  1110.     if substr(dname,1,1)  = "."  then return 0
  1111.     if cfg.ftpserver \= 2 & substr(flags,1,1) \= "d" then return 0
  1112.     if words(flags) > 1          then return 0
  1113.     if \isvalid(dname)           then return 0
  1114.     found = 0
  1115.     do i = 1 to inc.num
  1116.         if wildcardmatch(inc.i dname) then do
  1117.             found = 1
  1118.             leave
  1119.         end
  1120.     end
  1121.     if \found then return 0
  1122.     do i = 1 to skp.num
  1123.         if wildcardmatch(skp.i dname) then return 0
  1124.     end
  1125. return 1
  1126.  
  1127. /* Determine is filename valid or not */
  1128. isvalid: procedure expose glb.
  1129.     parse arg fname
  1130.     if fname = "" then return 0
  1131.     if words(fname) > 1 then return 0
  1132.     if verify(fname,"!#$%&'()+,-.01234567890;=@"||"ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_`"||"abcdefghijklmnopqrstuvwxyz{}~") \= 0 then return 0
  1133. return 1
  1134.  
  1135. /* Converts month in string representation into numeric form, i.e. Nov -> 11 */
  1136. month2dec: procedure expose glb.
  1137.     parse arg month
  1138.     select
  1139.         when month = "Jan" then return 1
  1140.         when month = "Feb" then return 2
  1141.         when month = "Mar" then return 3
  1142.         when month = "Apr" then return 4
  1143.         when month = "May" then return 5
  1144.         when month = "Jun" then return 6
  1145.         when month = "Jul" then return 7
  1146.         when month = "Aug" then return 8
  1147.         when month = "Sep" then return 9
  1148.         when month = "Oct" then return 10
  1149.         when month = "Nov" then return 11
  1150.         when month = "Dec" then return 12
  1151.         otherwise call saylog "Wrong month "month"; assuming January"
  1152.     end
  1153. return 1
  1154.  
  1155. /* Building date/time info for local file */
  1156. maketimestamp: procedure expose glb.
  1157.     parse arg dt tm
  1158.     year  = substr(right(dt,8),7,2)
  1159.     month = substr(right(dt,8),1,2)
  1160.     day   = substr(right(dt,8),4,2)
  1161.     hour  = substr(right(tm,6),1,2)
  1162.     min   = substr(right(tm,6),4,2)
  1163.     if (substr(right(tm,6),6,1) = "p" & hour \= 12) then hour = hour+12
  1164.     if substr(month,1,1) = " " then month = "0"substr(month,2,1)
  1165.     if substr(hour,1,1) = " " then hour = "0"substr(hour,2,1)
  1166. return "A"year||month||day||hour":"min
  1167.  
  1168. /* Converting remote file date/time information */
  1169. remotetimestamp: procedure expose glb. remfil.
  1170.     parse arg ind
  1171.     rday = remfil.ind.day
  1172.     if length(rday) = 1 then rday = "0"rday
  1173.     if substr(rday,1,1) = " " then rday = "0"substr(rday,2,1)
  1174.     rmonth = remfil.ind.month
  1175.     if length(rmonth) = 1 then rmonth = "0"rmonth
  1176.     if substr(rmonth,1,1) = " " then rmonth = "0"substr(rmonth,2,1)
  1177.     rtime = remfil.ind.time
  1178.     if length(rtime) = 4 then rtime = "0"rtime
  1179.     if substr(rtime,1,1) = " " then rtime = "0"substr(rtime,2,1)
  1180. return "A"substr(remfil.ind.year,3)||rmonth||rday||rtime
  1181.  
  1182. /* Create index key */
  1183. crtkey: procedure expose glb.
  1184.     parse arg val
  1185. return c2x(translate(strip(val,"T",".")))
  1186.  
  1187. /* Returns 1 if matched, 0 if not */
  1188. wildcardmatch: procedure expose glb.
  1189.     parse arg wildcard test
  1190.  
  1191.     if pos("*", substr(wildcard, pos("*", wildcard)+1)) \= 0 then do
  1192.         call saylog "Two or more asterisks in wildcard string; terminating"
  1193.         exit 2
  1194.     end
  1195.     if substr(wildcard, pos("*",wildcard)+1, 1) = "?" then do
  1196.         call saylog "Question mark right after asterisk in wildcard string; terminating"
  1197.         exit 2
  1198.     end
  1199.  
  1200.     it = 1
  1201.     iw = 1
  1202.     do forever
  1203.         p = compare(substr(wildcard,iw), substr(test,it))
  1204.         /*say "comparing["substr(wildcard,iw)"] and ["substr(test,it)"]; result is" p*/
  1205.         if p = 0 then return 1
  1206.         else do
  1207.             w = substr(wildcard,iw+p-1,1)
  1208.             if w = "?" then do /* "?" encountered */
  1209.                 if substr(test,it,1) = "" then return 0
  1210.                 iw = iw + p
  1211.                 it = it + p
  1212.                 iterate
  1213.             end
  1214.             else if w = "*" then do /* "*" encountered */
  1215.                 wp = substr(wildcard,iw+p)
  1216.                 /*say "wp is ["wp"]"*/
  1217.                 if wp = "" then return 1
  1218.                 iw = iw + p
  1219.                 nq = pos("?", wp)
  1220.                 /*say "nq is" nq*/
  1221.                 if nq = 0 then ss = wp
  1222.                 else ss = substr(wp, 1, nq-1)
  1223.                 /*say "searching ["ss"] in ["substr(test,it+p-1)"]"*/
  1224.                 it = it + p + pos(ss, substr(test,it+p)) - 1
  1225.             end
  1226.             else return 0 /* mismatch ! */
  1227.         end
  1228.     end
  1229.  
  1230. /* Convert string to lower case */
  1231. tolower: procedure expose glb.
  1232.     parse arg str
  1233.     cnvstr = str
  1234.     tblo = xrange('a','z')
  1235.     tbli = translate(tblo)
  1236.     cnvstr = translate(cnvstr, tblo||'ÖÄÅ', tbli||'öäå')
  1237. return cnvstr
  1238.  
  1239. /* Process arguments passed to REXX on command line */
  1240. prcopt: procedure expose glb. cfg.
  1241.     parse arg cmdarg
  1242.  
  1243.     optchr = "-"
  1244.     posarg = ""
  1245.     do i=1 to words(cmdarg)
  1246.         curword = word(cmdarg,i)
  1247.         if pos(left(curword,1), optchr) \= 0 then do
  1248.             curopt = translate(substr(curword,2))
  1249.             select
  1250.                 when (curopt='?') | (curopt='H') then do
  1251.                     call usage
  1252.                     exit 2
  1253.                 end
  1254.                 when (curopt='S') | (curopt='D') | (curopt='U') | (curopt='P') then do
  1255.                     i = i+1
  1256.                     if (i > words(cmdarg)) | (pos(left(word(cmdarg,i),1), optchr) \= 0) then do
  1257.                         say "Command line position "i-1": option value missing for '"curopt"'"
  1258.                         call usage
  1259.                         exit 2
  1260.                     end
  1261.  
  1262.                     select
  1263.                         when curopt='S' then cfg.serverhost = word(cmdarg,i)
  1264.                         when curopt='D' then cfg.startdir = word(cmdarg,i)
  1265.                         when curopt='U' then cfg.user = word(cmdarg,i)
  1266.                         when curopt='P' then cfg.passwd = word(cmdarg,i)
  1267.                     end
  1268.                 end
  1269.                 when curopt='L' then do
  1270.                     if pos("S",cfg.loglvl) = 0 then cfg.loglvl = cfg.loglvl||'S'
  1271.                     if (i+1 <= words(cmdarg)) & (pos(left(word(cmdarg,i+1),1), optchr) = 0) then do
  1272.                         i = i+1
  1273.                         cfg.scrlog = word(cmdarg,i)
  1274.                     end
  1275.                 end
  1276.                 otherwise do
  1277.                     say "Command line position "i": unsupported option '"curopt"'"
  1278.                     call usage
  1279.                     exit 2
  1280.                 end
  1281.             end
  1282.             iterate
  1283.         end
  1284.         posarg = posarg||' '||curword
  1285.     end
  1286.  
  1287.     posarg = strip(posarg)
  1288.     if posarg \= "" then do
  1289.         parse var posarg host dir userid password extra
  1290.         if host     \= "" then cfg.serverhost = host
  1291.         if dir      \= "" then cfg.startdir = dir
  1292.         if userid   \= "" then cfg.user = userid
  1293.         if password \= "" then cfg.passwd = password
  1294.         if extra \= "" then do
  1295.             say "Too many positional arguments, "words(posarg)" found (max 4): '"posarg"'"
  1296.             call usage
  1297.             exit 2
  1298.         end
  1299.     end
  1300. return
  1301.  
  1302. /* Usage of rxMirror: command line options */
  1303. usage: procedure expose glb.
  1304.     say "Usage: rxMirror [-?h] [[-s] hostname] [[-d] startdir] [[-u] userid]"
  1305.     say "                [[-p] passwd] [-l [scrlog]]"
  1306.     say
  1307.     say " ? or h     - display this help and exit"
  1308.     say " s hostname - hostname or IP number of the FTP server, no default"
  1309.     say " d startdir - directory from which mirroring starts;"
  1310.     say "              rxMirror will recurse into subdirs, no default"
  1311.     say " u userid   - login id on the remote server, default is anonymous"
  1312.     say " p passwd   - password on the ftp server, default is * (for prompt)"
  1313.     say " l scrlog   - screen output log. By default logging is disabled."
  1314.     say "              Default logname is '.mirror.scrlog' in current dir."
  1315.     say
  1316.     say "NOTE: For backward compatibility, some option tags are optional."
  1317. return
  1318.