Browse Source

Merge branch 'master' of github.com:OWeh55/kind

Wolfgang Ortmann 9 năm trước cách đây
mục cha
commit
1e2120e431
7 tập tin đã thay đổi với 131 bổ sung1725 xóa
  1. 9 5
      README.md
  2. BIN
      bin/kind
  3. 28 12
      man/kind.8
  4. 57 32
      man/kind.conf.5
  5. 22 13
      src/kind.ag
  6. 0 1658
      src/kind.cpp
  7. 15 5
      src/stringtools.cpp

+ 9 - 5
README.md

@@ -19,7 +19,7 @@ to the clients without password (using public key authentication).
 If the client runs rsync as server it must be configured to allow the
 If the client runs rsync as server it must be configured to allow the
 server to read the modules to backup. 
 server to read the modules to backup. 
 
 
-**Windows** clients can backed up using **DeltaCopy** as rsync server.
+**Windows** clients can be backed up using **DeltaCopy** as rsync server on Windows.
 
 
 ##Installation
 ##Installation
 
 
@@ -47,16 +47,20 @@ copy man/kind.conf.5 to /usr/share/man/man5
 
 
 ##Usage
 ##Usage
 - Create a directory used as "bank" on a filesystem with enough space
 - Create a directory used as "bank" on a filesystem with enough space
-- Create a master config file /etc/kind/master.conf, containing at least the bank:
+- Create a master config file /etc/kind/master.conf, containing at 
+least the bank:
 ```
 ```
     bank=/disk1/kind
     bank=/disk1/kind
 ```
 ```
-- Create a subdirectory of the bank as "vault" for one backup
-- Create a subdirectory kind in the vault directory
-- Create a vault config file /[bank]/[vault]/kind/vault.conf, containing
+- Create one or more subdirectories of the bank as "vault" for the backups
+- Create a subdirectory kind in each vault directory
+- Create a vault config files /[bank]/[vault]/kind/vault.conf, containing
 ```
 ```
     host=[client host]
     host=[client host]
+
     user=[backup user on client]
     user=[backup user on client]
+    path=[path to backup on client]
+
 ```
 ```
 - Run kind (on server) first time:
 - Run kind (on server) first time:
 ```
 ```

BIN
bin/kind


+ 28 - 12
man/kind.8

@@ -40,27 +40,30 @@ kind \- kind is no dirvish
 [
 [
 .I OPTIONS
 .I OPTIONS
 ]
 ]
-.I vault
+.I vault_or_group
 .SH DESCRIPTION
 .SH DESCRIPTION
 .P
 .P
-Create a backup image of a client directory tree.
+Create a backup image of a client directory tree. If configured
+a group name may be given as parameter to backup all group members.
 .P
 .P
-Each image is a directory containing transfer
+Each image is a directory containing
 .BR rsync-log,
 .BR rsync-log,
+.BR expires,
 .B tree
 .B tree
 and if errors were detected an
 and if errors were detected an
 .B error
 .B error
 file.
 file.
 The 
 The 
 .B rsync-log
 .B rsync-log
-retains the the output of rsync listing all files that were changed or added with some statistical information.
+retains the the output of rsync listing all files that were changed 
+or added with some statistical information.
 Tree is the copy of the client tree.
 Tree is the copy of the client tree.
 .P
 .P
 The client directory tree is compared with an existing image
 The client directory tree is compared with an existing image
 to create a new image.
 to create a new image.
 Unchanged files are shared between images.
 Unchanged files are shared between images.
-For changed files
-only those parts that actually change are transfered over the network.
+For changed files only those parts that actually change are 
+transfered over the network.
 Unchanged portions of files are copied from the reference image.
 Unchanged portions of files are copied from the reference image.
 .P
 .P
 The resulting images contain complete copies of the original trees
 The resulting images contain complete copies of the original trees
@@ -94,11 +97,24 @@ Don't actually do anything.
 Process all configuration files, options and tests
 Process all configuration files, options and tests
 then produce a summary on standard output and exit.
 then produce a summary on standard output and exit.
 .TP
 .TP
-.D backuponly
-Only backup, no expire
+.D backup
+Backup given vault[s]
 .TP
 .TP
-.D expireonly
-Only expire, no backup
+.D expire
+Expire given vault[s]
+.TP
+.D listconfig
+Show configuration for vault.
+.TP
+.D listimages
+Show data about all images of vault[s].
+
+if neither of --backup, --expire, --listconfig or --listimages is 
+given, kind does backup and expire.
+
+.TP
+.D help
+Print help and all available options.
 .TP
 .TP
 .D version
 .D version
 Print version string and exit.
 Print version string and exit.
@@ -128,11 +144,11 @@ actual image of source directory tree.
 .nf
 .nf
 ssh(1)
 ssh(1)
 rsync(1)
 rsync(1)
+kind.conf(5)
 .SH AUTHOR
 .SH AUTHOR
 kind was created by Wolfgang Ortmann.
 kind was created by Wolfgang Ortmann.
 .SH BUGS AND ISSUES
 .SH BUGS AND ISSUES
-In order to preserve permissions
-it is necessary for kind to run as root
+In order to preserve permissions it is necessary for kind to run as root
 on the backup server.
 on the backup server.
 
 
 For an automated backup process the root user must have 
 For an automated backup process the root user must have 

+ 57 - 32
man/kind.conf.5

@@ -75,8 +75,8 @@ Each value must be provided on its own line.
 Any leading and trailing whitespace is discarded for keys and values.
 Any leading and trailing whitespace is discarded for keys and values.
 Blank lines and lines starting with '#' are ignored.
 Blank lines and lines starting with '#' are ignored.
 
 
-On startup disu will first load a master configuration file
-.B /etc/d2/master.conf
+On startup kind will first load a master configuration file
+.B /etc/kind/master.conf
 
 
 Each vault must have an own configuration file, which must specify 
 Each vault must have an own configuration file, which must specify 
 at least the host and path to backup.
 at least the host and path to backup.
@@ -85,7 +85,7 @@ at least the host and path to backup.
 Boolean values need to specified as
 Boolean values need to specified as
 .B true
 .B true
 or
 or
-.B false
+.B false .
 Specifying an boolean option with an empty value is also interpreted as true.
 Specifying an boolean option with an empty value is also interpreted as true.
 
 
 Each option is marked here with one of (B) for Boolean, (S)
 Each option is marked here with one of (B) for Boolean, (S)
@@ -96,7 +96,7 @@ single value, (L) list.
 Specify paths to directories containing vaults.
 Specify paths to directories containing vaults.
 
 
 A \*[bank] is a directory containing one or more \*[vault]s.
 A \*[bank] is a directory containing one or more \*[vault]s.
-The system supports multiple \*[bank]s
+kind supports multiple \*[bank]s
 so that filesystem mount-points can be managed more effectively.
 so that filesystem mount-points can be managed more effectively.
 
 
 When a \*[vault] is specified the \*[bank]s will be searched
 When a \*[vault] is specified the \*[bank]s will be searched
@@ -107,15 +107,19 @@ or added without having to update a master index.
 .multiple bank
 .multiple bank
 .TP
 .TP
 .Bi host hostname (S)
 .Bi host hostname (S)
-specify a host from which to back up.
+specify a host to back up from using ssh. 
+
+.TP
+.Bi server hostname (S)
+specify a host running rsync as server to backup from.
 
 
 .TP
 .TP
 .Bi user username (S)
 .Bi user username (S)
-specify a user for login on host.
+specify a user for login on client.
 
 
 .TP
 .TP
 .Bi path path (S)
 .Bi path path (S)
-specify the path on host from which to back up. Use absolute path
+specify the path on client from which to back up. Use absolute path
 starting with '/'.
 starting with '/'.
 
 
 .TP
 .TP
@@ -129,7 +133,7 @@ Patterns are based on shell glob with some enhancements.
 .multiple exclude
 .multiple exclude
 .TP
 .TP
 .Bi userExcludeFile excludefilename (S)
 .Bi userExcludeFile excludefilename (S)
-Load a set of patterns from a file from host:path.
+Load a set of patterns from a file from client.
 
 
 .TP
 .TP
 .Bi expireFailedImage time period (S)
 .Bi expireFailedImage time period (S)
@@ -143,6 +147,13 @@ specify rules for expiration.
 .See "EXPIRE RULES"
 .See "EXPIRE RULES"
 
 
 .multiple expire\-rule
 .multiple expire\-rule
+.TP
+.Bi setRule backup set rule (L)
+This defines a backup set, which is another way to describe images and
+their lifetime.
+
+.See "BACKUP SETS"
+
 .TP
 .TP
 .Bi imageName name_of_images (S)
 .Bi imageName name_of_images (S)
 Specify a name for the \*[image]. The name must 
 Specify a name for the \*[image]. The name must 
@@ -152,7 +163,7 @@ image\-2016-03-09-20.
 
 
 .TP
 .TP
 .Bi longImageName true (B)
 .Bi longImageName true (B)
-Image time is added to the image name as YEAR-MONTH-DAY-hour.
+Normally Image time is added to the image name as YEAR-MONTH-DAY-hour.
 If longImageName is true the values for minute and second are added.
 If longImageName is true the values for minute and second are added.
 
 
 .default false
 .default false
@@ -174,12 +185,9 @@ This can be used to specify the location of
 .B ssh
 .B ssh
 or
 or
 .B rsh
 .B rsh
-and/or to provide additional options for said utility
-such as
-.Bi \-p port
-for
-.B ssh
-to use an alternate port number.
+and/or to provide additional options like in
+.Bi ssh \-p 2223
+to use ssh on an alternative port number.
 
 
 .default ssh
 .default ssh
 
 
@@ -191,7 +199,7 @@ and find excludes.
 .Bi rsyncOption option[s] (L)
 .Bi rsyncOption option[s] (L)
 Specify additional options for the rsync command.
 Specify additional options for the rsync command.
 
 
-This allows you to use rsync features that are not directly 
+This allows to use rsync features that are not directly 
 supported by kind.
 supported by kind.
 
 
 .multiple rsyncOption
 .multiple rsyncOption
@@ -213,12 +221,16 @@ This allows rules to be set in master configuration and
 rules in \*[vault] configuration files will override rules set in the
 rules in \*[vault] configuration files will override rules set in the
 master configuration file.
 master configuration file.
 
 
-Each rule has an pattern expression against which the image
-time is compared followed by a time period specifier.
+Expire time is determined at time of creation. Image time is compared
+to a pattern defined in expire rules and if the image time matches, 
+the rule is applied giving a lifetime of the image.
 
 
 Values for the image time pattern are hour, day of week, day of month
 Values for the image time pattern are hour, day of week, day of month
-and month. Each of these may be a single value or a '*', where '*' 
+and month. Each of these may be a single value, a range or a '*', where '*' 
 means "any value". 
 means "any value". 
+Values for weekday or month may be given as word like "monday" or "july".
+Ranges are given as 2-7, possibly extended by a distance like /2. 
+E.g. 2-15/3 means values 2, 5, 8, 11, 14.
 Time periods for expiring may be given in seconds or multiple of
 Time periods for expiring may be given in seconds or multiple of
 minute, hour, day, week, month(=30 days) or year(=365 days).
 minute, hour, day, week, month(=30 days) or year(=365 days).
 
 
@@ -227,30 +239,43 @@ Here are examples of a expire\-rules:
 .nf
 .nf
 .ft CR
 .ft CR
 .ta .5i T 6m
 .ta .5i T 6m
-	#hour	DayOfWeek DayOfMonth	Month	EXPIRE
-	*	*	1		1	5 years
-	*	sunday	*		*	1 month
-	*	*	16		*	1 year
-	10	*	*		*	1 days
-.ft R
+                #hour DayOfWeek DayOfMonth Month	EXPIRE
+expireRule =    20-5    *        1          1   5 years
+expireRule +=   *       sunday   *          *   1 month
+expireRule +=   *       *        */7        *   1 year
+expireRule +=   10      *        *          *   1 days
+.ft Rq
 .fi
 .fi
 
 
-It should be noted that (if not disabled) after backup all images
-are compared to expire rules and last match gives expire period. 
-If the expire date is reached, the image will be removed.
+.SH BACKUP SETS
+Backup sets are another way to describe lifetime of images. It is
+possible to define a number of backup sets, e.g. as "daily", "weekly" and
+"monthly". A backup set has a value for then time between to backups  
+and a value for the time to keep the images.
+
+This is given as
+.nf
+.ft CR
+.ta .5i T 6m
+#          name      rate     keep
+setRule =  daily:   1 day:   1 week
+setRule += weekly:  1 week:  1 months
+setRule += monthly: 1 month: 1 year
+.ft Rq
+.fi
 
 
 .SH FILES
 .SH FILES
 .TP
 .TP
-.B /etc/d2/master.conf
+.B /etc/kind/master.conf
 default master configuration file.
 default master configuration file.
 .TP
 .TP
-.IB bank/vault/ d2/vault.conf
+.B bank/vault/kind/vault.conf
 default vault configuration file.
 default vault configuration file.
 .TP
 .TP
-.IB bank/vault/image/ tree
+.B bank/vault/image/tree
 actual image of source directory tree.
 actual image of source directory tree.
 .TP
 .TP
-.IB bank/vault/image/ rsync-log
+.B bank/vault/image/rsync-log
 output from rsync
 output from rsync
 
 
 .SH SEE ALSO
 .SH SEE ALSO

+ 22 - 13
src/kind.ag

@@ -227,7 +227,7 @@ void listImageInfo(const string& vault,
   readVaultConfig(vault, conf);
   readVaultConfig(vault, conf);
   string vaultPath = findVault(vault);
   string vaultPath = findVault(vault);
   Images imageList = findImages(vaultPath, conf, true);
   Images imageList = findImages(vaultPath, conf, true);
-  cout << "---" << endl;
+  cout << "== " << vault << " ==" << endl;
   for (auto img : imageList)
   for (auto img : imageList)
     {
     {
       if (img.series == backupSet || backupSet.empty())
       if (img.series == backupSet || backupSet.empty())
@@ -673,13 +673,28 @@ int main(int argc, char* argv[])
       if (banks.empty())
       if (banks.empty())
         throw Exception("read master configuration", "no banks defined");
         throw Exception("read master configuration", "no banks defined");
 
 
+      vector<string> vaults;
+      string groupname = "group_" + vault;
+      if (conf.hasKey(groupname))
+        {
+          vaults = conf.getStrings(groupname);
+          vault.clear(); // no single vault but group
+        }
+      else
+        vaults.push_back(vault);
+
       if (listConfig)
       if (listConfig)
         {
         {
           cout << "global config:" << endl;
           cout << "global config:" << endl;
           conf.print(".   ");
           conf.print(".   ");
-          readVaultConfig(vault, conf);
-          cout << "vault config:" << endl;
-          conf.print(".   ");
+          if (!vault.empty())
+            {
+              readVaultConfig(vault, conf);
+              cout << "vault config:" << endl;
+              conf.print(".   ");
+            }
+          else
+            cout << "specify single vault (not group) to see vault config" << endl;
           exit(0);
           exit(0);
         }
         }
 
 
@@ -687,24 +702,18 @@ int main(int argc, char* argv[])
 
 
       if (listImages)
       if (listImages)
         {
         {
-          listImageInfo(vault, conf, imageTime, forcedBackupSet);
+          for (string vault : vaults)
+            listImageInfo(vault, conf, imageTime, forcedBackupSet);
           exit(0);
           exit(0);
         }
         }
 
 
+      // previous actions do not need locking
       lockFile = conf.getString("lockfile");
       lockFile = conf.getString("lockfile");
       createLock(lockFile);
       createLock(lockFile);
 
 
       string logSizeFile = conf.getString("logSize");
       string logSizeFile = conf.getString("logSize");
       readSizes(logSizeFile);
       readSizes(logSizeFile);
 
 
-      vector<string> vaults;
-      string groupname = "group_" + vault;
-      if (conf.hasKey(groupname))
-        vaults = conf.getStrings(groupname);
-      else
-        vaults.push_back(vault);
-
-
       for (string vault : vaults)
       for (string vault : vaults)
         {
         {
           if (doBackup)
           if (doBackup)

+ 0 - 1658
src/kind.cpp

@@ -1,1658 +0,0 @@
-# 1 "kind.ag"
-#include <dirent.h>
-# 2 "kind.ag"
-#include <sys/stat.h>
-# 3 "kind.ag"
-#include <cstring>
-# 4 "kind.ag"
-#include <unistd.h>
-# 5 "kind.ag"
-
-# 6 "kind.ag"
-#include <iostream>
-# 7 "kind.ag"
-#include <fstream>
-# 8 "kind.ag"
-
-# 9 "kind.ag"
-#include <string>
-# 10 "kind.ag"
-#include <vector>
-# 11 "kind.ag"
-#include <set>
-# 12 "kind.ag"
-
-# 13 "kind.ag"
-#include <algorithm>
-# 14 "kind.ag"
-
-# 15 "kind.ag"
-#include "stringtools.h"
-# 16 "kind.ag"
-#include "Exception.h"
-# 17 "kind.ag"
-
-# 18 "kind.ag"
-#include "DateTime.h"
-# 19 "kind.ag"
-#include "Strings.h"
-# 20 "kind.ag"
-#include "FileName.h"
-# 21 "kind.ag"
-#include "Image.h"
-# 22 "kind.ag"
-
-# 23 "kind.ag"
-#include "KindConfig.h"
-# 24 "kind.ag"
-#include "filetools.h"
-# 25 "kind.ag"
-#include "Lexer.h"
-# 26 "kind.ag"
-#include "rulecomp.h"
-# 27 "kind.ag"
-
-# 28 "kind.ag"
-#include "kind.h"
-# 29 "kind.ag"
-
-# 30 "kind.ag"
-#include "expiretools.h"
-# 31 "kind.ag"
-#include "excludetools.h"
-# 32 "kind.ag"
-
-# 33 "kind.ag"
-/*AppGen
-# 34 "kind.ag"
-  %%  Beschreibung des Programmes:
-# 35 "kind.ag"
-  prog: archiving backup
-# 36 "kind.ag"
-  %% Beschreibung Parameter
-# 37 "kind.ag"
-  % symbolischerName, Art, Typ,   Variablenname, Erklärung, Default-Wert
-# 38 "kind.ag"
-  para: vault_or_group, required, string, vault, Vault to backup
-# 39 "kind.ag"
-  %% Beschreibung der Optionen
-# 40 "kind.ag"
-  % kurz-Option, lang-Option, Typ, Variablenname, Erklärung, Default-Wert
-# 41 "kind.ag"
-  opt: c, masterconfig, string, masterConfig, Master config file, ""
-# 42 "kind.ag"
-  opt2: if not given or empty kind looks for
-# 43 "kind.ag"
-  opt2:   /etc/kind/master.conf
-# 44 "kind.ag"
-  opt2:   /ffp/etc/kind/master.conf
-# 45 "kind.ag"
-  opt: f, full, void, fullImage, Force full image == initial backup, false
-# 46 "kind.ag"
-  opt: B, backup, void, doBackup, Backup, false
-# 47 "kind.ag"
-  opt: E, expire, void, doExpire, Expire, false
-# 48 "kind.ag"
-  opt: C, listconfig, void, listConfig, Show configuration, false
-# 49 "kind.ag"
-  opt: I, listimages, void, listImages, List data of images, false
-# 50 "kind.ag"
-  opt2:   if none of backup, expire, listconfig and listimages is specified,
-# 51 "kind.ag"
-  opt2:   backup and expire is assumed.
-# 52 "kind.ag"
-  opt2:   listconfig and listimages cannot be combined with other actions
-# 53 "kind.ag"
-  opt: D, dryrun, Void, dryRun, Dry run (no real backup), false
-# 54 "kind.ag"
-  opt: F, forcebackup, string, forcedBackupSet, Create image for specified backup set, ""
-# 55 "kind.ag"
-  opt: v, verbose, Void, verbose,  Verbose,  false
-# 56 "kind.ag"
-  opt: d, debug, Void, debug, Debug output of many data, false
-# 57 "kind.ag"
-  opt: q, quiet, Void, quiet, Be quiet - no messages, false
-# 58 "kind.ag"
-  opt: h, help, usage, ignored , This help
-# 59 "kind.ag"
-AppGen*/
-# 60 "kind.ag"
-
-# 61 "kind.ag"
-using namespace std;
-# 62 "kind.ag"
-
-# 63 "kind.ag"
-/*AppGen:Global*/
-#include <getopt.h>
-#include <string>
-#include <string>
-#include <string>
-bool dryRun = false;
-bool verbose = false;
-bool debug = false;
-bool quiet = false;
-/*AppGen:GlobalEnd*/
-# 64 "kind.ag"
-
-# 65 "kind.ag"
-Strings banks;
-# 66 "kind.ag"
-
-# 67 "kind.ag"
-string findVault(const string& v);
-# 68 "kind.ag"
-
-# 69 "kind.ag"
-// we use double for sizes (in byte) to avoid overflow
-# 70 "kind.ag"
-// on machines with small int types
-# 71 "kind.ag"
-typedef pair<double, double> Sizes;
-# 72 "kind.ag"
-map<string, Sizes> sizes;
-# 73 "kind.ag"
-
-# 74 "kind.ag"
-void readSizes(const string& logSizeFile)
-# 75 "kind.ag"
-{
-# 76 "kind.ag"
-  if (!logSizeFile.empty() && fileExists(logSizeFile))
-# 77 "kind.ag"
-    {
-# 78 "kind.ag"
-      vector<string> ss;
-# 79 "kind.ag"
-      file2Strings(logSizeFile, ss);
-# 80 "kind.ag"
-      for (const string& s : ss)
-# 81 "kind.ag"
-        {
-# 82 "kind.ag"
-          unsigned int i = 0;
-# 83 "kind.ag"
-          string vault = getWord(s, i);
-# 84 "kind.ag"
-          double s1 = getDouble(s, i);
-# 85 "kind.ag"
-          double s2 = getDouble(s, i);
-# 86 "kind.ag"
-          try
-# 87 "kind.ag"
-            {
-# 88 "kind.ag"
-              findVault(vault);
-# 89 "kind.ag"
-              sizes[vault] = Sizes(s1, s2);
-# 90 "kind.ag"
-            }
-# 91 "kind.ag"
-          catch (...)
-# 92 "kind.ag"
-            {
-# 93 "kind.ag"
-              // ignore missing vaults
-# 94 "kind.ag"
-            }
-# 95 "kind.ag"
-        }
-# 96 "kind.ag"
-    }
-# 97 "kind.ag"
-}
-# 98 "kind.ag"
-
-# 99 "kind.ag"
-void writeSizes(const string logSizeFile)
-# 100 "kind.ag"
-{
-# 101 "kind.ag"
-  if (!logSizeFile.empty())
-# 102 "kind.ag"
-    {
-# 103 "kind.ag"
-      Strings st;
-# 104 "kind.ag"
-      for (auto s : sizes)
-# 105 "kind.ag"
-        {
-# 106 "kind.ag"
-          string h = s.first + " " + to_string(s.second.first) + " " + to_string(s.second.second);
-# 107 "kind.ag"
-          st.push_back(h);
-# 108 "kind.ag"
-        }
-# 109 "kind.ag"
-      strings2File(st, logSizeFile);
-# 110 "kind.ag"
-    }
-# 111 "kind.ag"
-}
-# 112 "kind.ag"
-
-# 113 "kind.ag"
-void verbosePrint(const string& text)
-# 114 "kind.ag"
-{
-# 115 "kind.ag"
-  if (verbose)
-# 116 "kind.ag"
-    cout << "  " << text << endl;
-# 117 "kind.ag"
-}
-# 118 "kind.ag"
-
-# 119 "kind.ag"
-void debugPrint(const string& text)
-# 120 "kind.ag"
-{
-# 121 "kind.ag"
-  if (debug)
-# 122 "kind.ag"
-    cout << "    " << text << endl;
-# 123 "kind.ag"
-}
-# 124 "kind.ag"
-
-# 125 "kind.ag"
-void readMasterConfig1(const string& fn, KindConfig& conf)
-# 126 "kind.ag"
-{
-# 127 "kind.ag"
-  verbosePrint("reading master config " + fn);
-# 128 "kind.ag"
-  conf.addFile(fn);
-# 129 "kind.ag"
-}
-# 130 "kind.ag"
-
-# 131 "kind.ag"
-void readMasterConfig(const string& fn, KindConfig& conf)
-# 132 "kind.ag"
-{
-# 133 "kind.ag"
-  if (!fn.empty())  // master config given by user on commandline
-# 134 "kind.ag"
-    readMasterConfig1(fn, conf);
-# 135 "kind.ag"
-  else if (fileExists("/etc/kind/master.conf"))
-# 136 "kind.ag"
-    readMasterConfig1("/etc/kind/master.conf", conf);
-# 137 "kind.ag"
-  else if (fileExists("/ffp/etc/kind/master.conf"))
-# 138 "kind.ag"
-    readMasterConfig1("/ffp/etc/kind/master.conf", conf);
-# 139 "kind.ag"
-  else
-# 140 "kind.ag"
-    throw Exception("MasterConfig", "no file");
-# 141 "kind.ag"
-}
-# 142 "kind.ag"
-
-# 143 "kind.ag"
-string findVault(const string& v)
-# 144 "kind.ag"
-{
-# 145 "kind.ag"
-  bool found = false;
-# 146 "kind.ag"
-  FileName fn;
-# 147 "kind.ag"
-  fn.setName(v);
-# 148 "kind.ag"
-  for (unsigned int i = 0; !found && i < banks.size(); ++i)
-# 149 "kind.ag"
-    {
-# 150 "kind.ag"
-      fn.setPath(banks[i]);
-# 151 "kind.ag"
-      if (dirExists(fn.getFileName()))
-# 152 "kind.ag"
-        found = true;
-# 153 "kind.ag"
-    }
-# 154 "kind.ag"
-  if (!found)
-# 155 "kind.ag"
-    throw Exception("find vault", v + " not found");
-# 156 "kind.ag"
-  verbosePrint("using vault " + fn.getFileName());
-# 157 "kind.ag"
-  return fn.getFileName();
-# 158 "kind.ag"
-}
-# 159 "kind.ag"
-
-# 160 "kind.ag"
-void readVaultConfig(const string& vault, KindConfig& conf)
-# 161 "kind.ag"
-{
-# 162 "kind.ag"
-  string vaultpath = findVault(vault);
-# 163 "kind.ag"
-  const string& vaultConfigName = vaultpath + '/' + conf.getString("vaultConfigName");
-# 164 "kind.ag"
-  verbosePrint("reading vault config:");
-# 165 "kind.ag"
-  verbosePrint("  " + vaultConfigName);
-# 166 "kind.ag"
-  conf.addFile(vaultConfigName);
-# 167 "kind.ag"
-}
-# 168 "kind.ag"
-
-# 169 "kind.ag"
-string getImageName(const KindConfig& conf,
-# 170 "kind.ag"
-                    const string& vaultPath,
-# 171 "kind.ag"
-                    const DateTime& imageTime)
-# 172 "kind.ag"
-{
-# 173 "kind.ag"
-  bool nonPortable = false;
-# 174 "kind.ag"
-  string imageName = conf.getString("imageName");
-# 175 "kind.ag"
-  for (unsigned int i = 0; !nonPortable && i < imageName.size(); ++i)
-# 176 "kind.ag"
-    {
-# 177 "kind.ag"
-      char c = imageName[i];
-# 178 "kind.ag"
-      if (!isalnum(c) && c != '.' && c != '_')
-# 179 "kind.ag"
-        nonPortable = true;
-# 180 "kind.ag"
-    }
-# 181 "kind.ag"
-  if (nonPortable)
-# 182 "kind.ag"
-    throw Exception("getImageName", "Invalid character in image name " + imageName);
-# 183 "kind.ag"
-
-# 184 "kind.ag"
-  if (!imageName.empty())
-# 185 "kind.ag"
-    imageName += '-';
-# 186 "kind.ag"
-
-# 187 "kind.ag"
-  string imageFullName =  vaultPath + "/" + imageName ;
-# 188 "kind.ag"
-
-# 189 "kind.ag"
-  if (conf.getBool("longImageName"))
-# 190 "kind.ag"
-    imageFullName += imageTime.getString('m');
-# 191 "kind.ag"
-  else
-# 192 "kind.ag"
-    imageFullName += imageTime.getString('s');
-# 193 "kind.ag"
-
-# 194 "kind.ag"
-  return imageFullName;
-# 195 "kind.ag"
-}
-# 196 "kind.ag"
-
-# 197 "kind.ag"
-Images findImages(const string& vaultpath, const KindConfig& conf, bool all)
-# 198 "kind.ag"
-{
-# 199 "kind.ag"
-  Strings dirs;
-# 200 "kind.ag"
-  debugPrint("searching images in " + vaultpath);
-# 201 "kind.ag"
-  dirList(vaultpath, dirs);
-# 202 "kind.ag"
-
-# 203 "kind.ag"
-  Images imageList;
-# 204 "kind.ag"
-  for (string dir : dirs)
-# 205 "kind.ag"
-    {
-# 206 "kind.ag"
-      FileName fn(dir);
-# 207 "kind.ag"
-      string imgname = conf.getString("imageName");
-# 208 "kind.ag"
-      if (startsWith(fn.getName(), imgname))
-# 209 "kind.ag"
-        {
-# 210 "kind.ag"
-          debugPrint("Checking " + dir);
-# 211 "kind.ag"
-          Image image(dir);
-# 212 "kind.ag"
-
-# 213 "kind.ag"
-          if (all || image.valid)
-# 214 "kind.ag"
-            imageList.push_back(image);
-# 215 "kind.ag"
-        }
-# 216 "kind.ag"
-    }
-# 217 "kind.ag"
-  if (imageList.size() > 1)
-# 218 "kind.ag"
-    sort(imageList.begin(), imageList.end());
-# 219 "kind.ag"
-  return imageList;
-# 220 "kind.ag"
-}
-# 221 "kind.ag"
-
-# 222 "kind.ag"
-void listImageInfo(const string& vault,
-# 223 "kind.ag"
-                   KindConfig conf /*Copy!*/ ,
-# 224 "kind.ag"
-                   const DateTime& imageTime,
-# 225 "kind.ag"
-                   const string& backupSet)
-# 226 "kind.ag"
-{
-# 227 "kind.ag"
-  readVaultConfig(vault, conf);
-# 228 "kind.ag"
-  string vaultPath = findVault(vault);
-# 229 "kind.ag"
-  Images imageList = findImages(vaultPath, conf, true);
-# 230 "kind.ag"
-  cout << "---" << endl;
-# 231 "kind.ag"
-  for (auto img : imageList)
-# 232 "kind.ag"
-    {
-# 233 "kind.ag"
-      if (img.series == backupSet || backupSet.empty())
-# 234 "kind.ag"
-        {
-# 235 "kind.ag"
-          img.printInfo();
-# 236 "kind.ag"
-          cout << "---" << endl;
-# 237 "kind.ag"
-        }
-# 238 "kind.ag"
-    }
-# 239 "kind.ag"
-}
-# 240 "kind.ag"
-
-# 241 "kind.ag"
-void doBackup(const string& vault,
-# 242 "kind.ag"
-              const string& imageFullName,
-# 243 "kind.ag"
-              const string& referenceImage,
-# 244 "kind.ag"
-              const KindConfig& conf)
-# 245 "kind.ag"
-{
-# 246 "kind.ag"
-  // create image path
-# 247 "kind.ag"
-
-# 248 "kind.ag"
-  bool shellMode = true;
-# 249 "kind.ag"
-
-# 250 "kind.ag"
-  // create source descriptor
-# 251 "kind.ag"
-  string host;
-# 252 "kind.ag"
-  if (conf.hasKey("host"))
-# 253 "kind.ag"
-    host = conf.getString("host");
-# 254 "kind.ag"
-
-# 255 "kind.ag"
-  string server;
-# 256 "kind.ag"
-  if (conf.hasKey("server"))
-# 257 "kind.ag"
-    {
-# 258 "kind.ag"
-      server = conf.getString("server");
-# 259 "kind.ag"
-      shellMode = false;
-# 260 "kind.ag"
-    }
-# 261 "kind.ag"
-
-# 262 "kind.ag"
-  if (!host.empty() && !server.empty())
-# 263 "kind.ag"
-    throw Exception("backupVault", "Cannot have host and server");
-# 264 "kind.ag"
-
-# 265 "kind.ag"
-  if (host.empty() && server.empty())
-# 266 "kind.ag"
-    throw Exception("backupVault", "No host or server specified");
-# 267 "kind.ag"
-
-# 268 "kind.ag"
-  // ping host / server
-# 269 "kind.ag"
-  // ping -c 1 -W 5 -q $HOST
-# 270 "kind.ag"
-  string pingCommand = conf.getString("ping");
-# 271 "kind.ag"
-  debugPrint("PingCommand: " + pingCommand);
-# 272 "kind.ag"
-  if (!pingCommand.empty())
-# 273 "kind.ag"
-    {
-# 274 "kind.ag"
-      if (!host.empty())
-# 275 "kind.ag"
-        replacePlaceHolder(pingCommand, "%host", host);
-# 276 "kind.ag"
-      else
-# 277 "kind.ag"
-        replacePlaceHolder(pingCommand, "%host", server);
-# 278 "kind.ag"
-      int rc = 0;
-# 279 "kind.ag"
-      Strings pingResult = localExec(pingCommand, rc, debug);
-# 280 "kind.ag"
-      if (rc != 0)
-# 281 "kind.ag"
-        throw Exception("Host not available", pingCommand);
-# 282 "kind.ag"
-    }
-# 283 "kind.ag"
-
-# 284 "kind.ag"
-  string path = conf.getString("path");
-# 285 "kind.ag"
-  if (path.empty())
-# 286 "kind.ag"
-    throw Exception("rsync", "empty source path");
-# 287 "kind.ag"
-  if (path.back() != '/')
-# 288 "kind.ag"
-    path += '/';
-# 289 "kind.ag"
-
-# 290 "kind.ag"
-  string rsyncCmd = "rsync -vrltH --delete --stats -D --numeric-ids ";
-# 291 "kind.ag"
-  if (!conf.getBool("ignorePermission"))
-# 292 "kind.ag"
-    rsyncCmd += "-pgo";
-# 293 "kind.ag"
-  vector<string> rso = conf.getStrings("rsyncOption");
-# 294 "kind.ag"
-  for (const string& opt : rso)
-# 295 "kind.ag"
-    rsyncCmd += opt + " ";
-# 296 "kind.ag"
-
-# 297 "kind.ag"
-  // excludes
-# 298 "kind.ag"
-  Strings excluded = getExclusions(conf, shellMode);
-# 299 "kind.ag"
-
-# 300 "kind.ag"
-  // create image path
-# 301 "kind.ag"
-  if (!dryRun)
-# 302 "kind.ag"
-    if (mkdir(imageFullName.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
-# 303 "kind.ag"
-      throw Exception("Create image", "failed to create " + imageFullName);
-# 304 "kind.ag"
-
-# 305 "kind.ag"
-  // error message
-# 306 "kind.ag"
-  // we write an generic error message to mark backup as unsuccessful
-# 307 "kind.ag"
-  // will be deleted at successful end of rsync
-# 308 "kind.ag"
-  string errorfile = imageFullName + "/error";
-# 309 "kind.ag"
-  if (!dryRun)
-# 310 "kind.ag"
-    {
-# 311 "kind.ag"
-      ofstream error(errorfile);
-# 312 "kind.ag"
-      error << "failed" << endl;
-# 313 "kind.ag"
-      error.close();
-# 314 "kind.ag"
-    }
-# 315 "kind.ag"
-
-# 316 "kind.ag"
-  if (shellMode)  // shell mode
-# 317 "kind.ag"
-    {
-# 318 "kind.ag"
-      // cout << "USING SHELLMODE '" << host << "'" << endl;
-# 319 "kind.ag"
-      string remoteShell = conf.getString("remoteShell");
-# 320 "kind.ag"
-      string userAtHost = conf.getString("user") + "@" + conf.getString("host");
-# 321 "kind.ag"
-      string rshCommand = remoteShell;
-# 322 "kind.ag"
-      if (remoteShell.empty())
-# 323 "kind.ag"
-        rshCommand = "ssh";
-# 324 "kind.ag"
-
-# 325 "kind.ag"
-      rshCommand += " " + userAtHost;
-# 326 "kind.ag"
-
-# 327 "kind.ag"
-      if (!dryRun)
-# 328 "kind.ag"
-        strings2File(excluded, imageFullName + "/exclude");
-# 329 "kind.ag"
-
-# 330 "kind.ag"
-      // rsync image
-# 331 "kind.ag"
-
-# 332 "kind.ag"
-      if (!remoteShell.empty())
-# 333 "kind.ag"
-        rsyncCmd += " -e \'" + remoteShell + "\' ";
-# 334 "kind.ag"
-
-# 335 "kind.ag"
-      rsyncCmd += "--exclude-from=" + imageFullName + "/exclude ";
-# 336 "kind.ag"
-      if (!referenceImage.empty())
-# 337 "kind.ag"
-        rsyncCmd += "--link-dest=" + referenceImage + "/tree ";
-# 338 "kind.ag"
-      rsyncCmd += userAtHost + ":" + path + " ";
-# 339 "kind.ag"
-      rsyncCmd += imageFullName + "/tree";
-# 340 "kind.ag"
-    } // shell mode
-# 341 "kind.ag"
-  else
-# 342 "kind.ag"
-    {
-# 343 "kind.ag"
-      // cout << "USING SERVERMODE" << endl;
-# 344 "kind.ag"
-      // we cannot use find without shell access
-# 345 "kind.ag"
-      // and do not read an exclude file on client side
-# 346 "kind.ag"
-
-# 347 "kind.ag"
-      if (!dryRun)
-# 348 "kind.ag"
-        strings2File(excluded, imageFullName + "/exclude");
-# 349 "kind.ag"
-
-# 350 "kind.ag"
-      rsyncCmd += "--exclude-from=" + imageFullName + "/exclude ";
-# 351 "kind.ag"
-      if (!referenceImage.empty())
-# 352 "kind.ag"
-        rsyncCmd += "--link-dest=" + referenceImage + "/tree ";
-# 353 "kind.ag"
-      rsyncCmd += conf.getString("server") + "::" + path + " ";
-# 354 "kind.ag"
-      rsyncCmd += imageFullName + "/tree";
-# 355 "kind.ag"
-    }
-# 356 "kind.ag"
-
-# 357 "kind.ag"
-  debugPrint("Action: " + rsyncCmd);
-# 358 "kind.ag"
-
-# 359 "kind.ag"
-  vector<string> backupResult;
-# 360 "kind.ag"
-  if (!dryRun)
-# 361 "kind.ag"
-    {
-# 362 "kind.ag"
-      verbosePrint("syncing (" + rsyncCmd + ")");
-# 363 "kind.ag"
-      int rc;
-# 364 "kind.ag"
-      backupResult = localExec(rsyncCmd, rc, debug, imageFullName + "/rsync-log");
-# 365 "kind.ag"
-      if (rc == 0 ||
-# 366 "kind.ag"
-          rc == 24 || // "no error" or "vanished source files" (ignored)
-# 367 "kind.ag"
-          rc == 6144) // workaround for wrong exit code ??!!
-# 368 "kind.ag"
-        {
-# 369 "kind.ag"
-          unlink(errorfile.c_str());
-# 370 "kind.ag"
-          double st = 0;
-# 371 "kind.ag"
-          double sc = 0;
-# 372 "kind.ag"
-          for (auto bl : backupResult)
-# 373 "kind.ag"
-            {
-# 374 "kind.ag"
-              if (startsWith(bl, "Total file size"))
-# 375 "kind.ag"
-                st = getNumber(bl);
-# 376 "kind.ag"
-              else if (startsWith(bl, "Total transferred file size"))
-# 377 "kind.ag"
-                sc = getNumber(bl);
-# 378 "kind.ag"
-            }
-# 379 "kind.ag"
-          // sizes[vault] = pair<long int, long int>(st, sc);
-# 380 "kind.ag"
-          sizes[vault] = Sizes(st, sc);
-# 381 "kind.ag"
-          //  cout << vault << " " << st << " || " << sc << endl;
-# 382 "kind.ag"
-        }
-# 383 "kind.ag"
-      else
-# 384 "kind.ag"
-        throw Exception("Backup", "Failed to execute rsync (result: " + to_string(rc) + ")");
-# 385 "kind.ag"
-    }
-# 386 "kind.ag"
-  else
-# 387 "kind.ag"
-    cout << "Not executing " << rsyncCmd << endl;
-# 388 "kind.ag"
-}
-# 389 "kind.ag"
-
-# 390 "kind.ag"
-bool backupVault(const string& vault,
-# 391 "kind.ag"
-                 KindConfig conf /*Copy!*/ ,
-# 392 "kind.ag"
-                 const DateTime& imageTime,
-# 393 "kind.ag"
-                 bool fullImage,
-# 394 "kind.ag"
-                 const string& forcedBackupSet)
-# 395 "kind.ag"
-{
-# 396 "kind.ag"
-  if (!quiet)
-# 397 "kind.ag"
-    cout << DateTime::now().getString('h') << ": Backup of vault " << vault << endl;
-# 398 "kind.ag"
-  try
-# 399 "kind.ag"
-    {
-# 400 "kind.ag"
-      readVaultConfig(vault, conf);
-# 401 "kind.ag"
-
-# 402 "kind.ag"
-      // where to store
-# 403 "kind.ag"
-      string vaultPath = findVault(vault);
-# 404 "kind.ag"
-
-# 405 "kind.ag"
-      // image path
-# 406 "kind.ag"
-      string imageFullName = getImageName(conf, vaultPath, imageTime);
-# 407 "kind.ag"
-
-# 408 "kind.ag"
-      bool backupNow = true;
-# 409 "kind.ag"
-
-# 410 "kind.ag"
-      // existing images
-# 411 "kind.ag"
-      Images validImageList = findImages(vaultPath, conf, false);
-# 412 "kind.ag"
-      string currentSet = "expire"; // we are not using backupSets
-# 413 "kind.ag"
-
-# 414 "kind.ag"
-      // check if we are using backup sets
-# 415 "kind.ag"
-
-# 416 "kind.ag"
-      map<string, int> setIdx;
-# 417 "kind.ag"
-      vector<SetRule> backupSetRule;
-# 418 "kind.ag"
-      int setRuleIdx = -1;
-# 419 "kind.ag"
-
-# 420 "kind.ag"
-      if (conf.hasKey("setRule"))
-# 421 "kind.ag"
-        {
-# 422 "kind.ag"
-          readSetRules(conf, setIdx, backupSetRule);
-# 423 "kind.ag"
-          if (!setIdx.empty())
-# 424 "kind.ag"
-            {
-# 425 "kind.ag"
-              if (forcedBackupSet.empty())
-# 426 "kind.ag"
-                {
-# 427 "kind.ag"
-                  backupNow = false;
-# 428 "kind.ag"
-
-# 429 "kind.ag"
-                  // find time for nextBackup for every backupSet
-# 430 "kind.ag"
-                  // defaults to now == imageTime;
-# 431 "kind.ag"
-                  vector<DateTime> nextBackup(backupSetRule.size(), imageTime);
-# 432 "kind.ag"
-
-# 433 "kind.ag"
-                  // find time for next backup
-# 434 "kind.ag"
-
-# 435 "kind.ag"
-                  for (const Image& image : validImageList)
-# 436 "kind.ag"
-                    {
-# 437 "kind.ag"
-                      if (image.series != "expire")
-# 438 "kind.ag"
-                        {
-# 439 "kind.ag"
-                          string s = image.series;
-# 440 "kind.ag"
-                          if (setIdx.count(s) > 0) // rule for set exists?
-# 441 "kind.ag"
-                            {
-# 442 "kind.ag"
-                              int rIdx = setIdx[s];
-# 443 "kind.ag"
-                              // image is valid for this and "lower level" backupSets
-# 444 "kind.ag"
-                              for (unsigned int i = rIdx; i < backupSetRule.size(); ++i)
-# 445 "kind.ag"
-                                if (nextBackup[i] < image.time + backupSetRule[i].distance)
-# 446 "kind.ag"
-                                  nextBackup[i] =  image.time + backupSetRule[i].distance;
-# 447 "kind.ag"
-                            }
-# 448 "kind.ag"
-                        }
-# 449 "kind.ag"
-                    }
-# 450 "kind.ag"
-                  if (debug)
-# 451 "kind.ag"
-                    for (unsigned int i = 0; i < backupSetRule.size(); ++i)
-# 452 "kind.ag"
-                      cout << "       Next backup for " << backupSetRule[i].name << " at " <<  nextBackup[i].getString('h') << endl;
-# 453 "kind.ag"
-
-# 454 "kind.ag"
-                  // find backupSet that
-# 455 "kind.ag"
-                  //    - needs backup
-# 456 "kind.ag"
-                  //    - has longest time to keep
-# 457 "kind.ag"
-                  // because of ordered list backupSetRule this is the first set, that need
-# 458 "kind.ag"
-
-# 459 "kind.ag"
-                  currentSet = "";
-# 460 "kind.ag"
-                  for (unsigned int i = 0; i < backupSetRule.size() && currentSet.empty(); ++i)
-# 461 "kind.ag"
-                    {
-# 462 "kind.ag"
-                      string name = backupSetRule[i].name;
-# 463 "kind.ag"
-                      if (nextBackup[i] <= imageTime + 5) // small offset of 5s for "jitter"
-# 464 "kind.ag"
-                        {
-# 465 "kind.ag"
-                          backupNow = true;
-# 466 "kind.ag"
-                          currentSet = name;
-# 467 "kind.ag"
-                          setRuleIdx = i;
-# 468 "kind.ag"
-                        }
-# 469 "kind.ag"
-                    }
-# 470 "kind.ag"
-                }
-# 471 "kind.ag"
-              else
-# 472 "kind.ag"
-                {
-# 473 "kind.ag"
-                  if (setIdx.count(forcedBackupSet) > 0)
-# 474 "kind.ag"
-                    {
-# 475 "kind.ag"
-                      currentSet = forcedBackupSet;
-# 476 "kind.ag"
-                      setRuleIdx = setIdx[forcedBackupSet];
-# 477 "kind.ag"
-                    }
-# 478 "kind.ag"
-                  else
-# 479 "kind.ag"
-                    throw Exception("force backup of set " + forcedBackupSet, " set not exists");
-# 480 "kind.ag"
-                }
-# 481 "kind.ag"
-            } // if (!setIdx.empty())
-# 482 "kind.ag"
-        } // (conf.hasKey("setRule"))
-# 483 "kind.ag"
-
-# 484 "kind.ag"
-      if (backupNow)
-# 485 "kind.ag"
-        {
-# 486 "kind.ag"
-          verbosePrint("backup to \"" + imageFullName + "\"");
-# 487 "kind.ag"
-          if (setRuleIdx >= 0 && !quiet)
-# 488 "kind.ag"
-            cout << "  backup set is \"" << currentSet << "\"" << endl;
-# 489 "kind.ag"
-        }
-# 490 "kind.ag"
-      else if (!quiet)
-# 491 "kind.ag"
-        cout << "  no backup set needs update" << endl;
-# 492 "kind.ag"
-
-# 493 "kind.ag"
-      if (backupNow)
-# 494 "kind.ag"
-        {
-# 495 "kind.ag"
-          // find reference image
-# 496 "kind.ag"
-          string referenceImage;
-# 497 "kind.ag"
-          if (!fullImage)
-# 498 "kind.ag"
-            {
-# 499 "kind.ag"
-              if (validImageList.empty())
-# 500 "kind.ag"
-                throw Exception("backupVault", "no reference image found");
-# 501 "kind.ag"
-              // last image is newest image
-# 502 "kind.ag"
-              referenceImage = validImageList.back().name;
-# 503 "kind.ag"
-            }
-# 504 "kind.ag"
-
-# 505 "kind.ag"
-          doBackup(vault, imageFullName, referenceImage, conf);
-# 506 "kind.ag"
-
-# 507 "kind.ag"
-          if (!dryRun)
-# 508 "kind.ag"
-            {
-# 509 "kind.ag"
-              string lastPath = vaultPath + "/last";
-# 510 "kind.ag"
-              struct stat fstat;
-# 511 "kind.ag"
-
-# 512 "kind.ag"
-              // remove last (dir or symlink)
-# 513 "kind.ag"
-              if (lstat(lastPath.c_str(), &fstat) == 0) // last exists
-# 514 "kind.ag"
-                {
-# 515 "kind.ag"
-                  if (S_ISDIR(fstat.st_mode))
-# 516 "kind.ag"
-                    removeDir(lastPath);
-# 517 "kind.ag"
-                  else
-# 518 "kind.ag"
-                    unlink(lastPath.c_str());
-# 519 "kind.ag"
-                }
-# 520 "kind.ag"
-
-# 521 "kind.ag"
-              string linkType = conf.getString("lastLink");
-# 522 "kind.ag"
-              if (linkType == "hardLink")
-# 523 "kind.ag"
-                {
-# 524 "kind.ag"
-                  int rc;
-# 525 "kind.ag"
-                  string hardLinkCommand = "cp -al " + imageFullName + " " + lastPath;
-# 526 "kind.ag"
-                  Strings res = localExec(hardLinkCommand, rc, debug);
-# 527 "kind.ag"
-                }
-# 528 "kind.ag"
-              else if (linkType == "symLink")
-# 529 "kind.ag"
-                {
-# 530 "kind.ag"
-                  // set symlink to last image
-# 531 "kind.ag"
-                  symlink(imageFullName.c_str(), lastPath.c_str());
-# 532 "kind.ag"
-                }
-# 533 "kind.ag"
-              else if (linkType != "null")
-# 534 "kind.ag"
-                cerr << "invalid Value in \"lastLink\"" << endl;
-# 535 "kind.ag"
-
-# 536 "kind.ag"
-              // write expire date to file
-# 537 "kind.ag"
-              DateTime expireTime;
-# 538 "kind.ag"
-              string rule;
-# 539 "kind.ag"
-              if (setRuleIdx < 0) // not backup set based
-# 540 "kind.ag"
-                expireTime = getExpireDate(imageTime, conf, rule);
-# 541 "kind.ag"
-              else
-# 542 "kind.ag"
-                {
-# 543 "kind.ag"
-                  expireTime = imageTime + backupSetRule[setRuleIdx].keep;
-# 544 "kind.ag"
-                  rule =  backupSetRule[setRuleIdx].rule;
-# 545 "kind.ag"
-                }
-# 546 "kind.ag"
-
-# 547 "kind.ag"
-              ofstream expireFile(imageFullName + "/expires");
-# 548 "kind.ag"
-              expireFile << currentSet << "-" << expireTime.getString('m') << endl;
-# 549 "kind.ag"
-              expireFile << rule << endl;
-# 550 "kind.ag"
-            }
-# 551 "kind.ag"
-        }
-# 552 "kind.ag"
-      return backupNow;
-# 553 "kind.ag"
-    }
-# 554 "kind.ag"
-  catch (Exception ex)
-# 555 "kind.ag"
-    {
-# 556 "kind.ag"
-      cerr << "Exception in vault " << vault << ": " << ex.what() << endl;
-# 557 "kind.ag"
-      return false;
-# 558 "kind.ag"
-    }
-# 559 "kind.ag"
-}
-# 560 "kind.ag"
-
-# 561 "kind.ag"
-void expireVault(const string& vault, KindConfig conf, DateTime now)
-# 562 "kind.ag"
-{
-# 563 "kind.ag"
-  if (!quiet)
-# 564 "kind.ag"
-    cout << DateTime::now().getString('h') << ": Expiring images in vault " << vault << endl;
-# 565 "kind.ag"
-
-# 566 "kind.ag"
-  readVaultConfig(vault, conf);
-# 567 "kind.ag"
-
-# 568 "kind.ag"
-  string vaultpath = findVault(vault);
-# 569 "kind.ag"
-
-# 570 "kind.ag"
-  Images imagelist = findImages(vaultpath, conf, true);
-# 571 "kind.ag"
-
-# 572 "kind.ag"
-  string lastValidImage;
-# 573 "kind.ag"
-  for (Image image : imagelist)
-# 574 "kind.ag"
-    {
-# 575 "kind.ag"
-      if (image.valid)
-# 576 "kind.ag"
-        lastValidImage = image.name;
-# 577 "kind.ag"
-    }
-# 578 "kind.ag"
-
-# 579 "kind.ag"
-  for (Image image : imagelist)
-# 580 "kind.ag"
-    {
-# 581 "kind.ag"
-      if (debug)
-# 582 "kind.ag"
-        image.printInfo();
-# 583 "kind.ag"
-
-# 584 "kind.ag"
-      DateTime imageTime = image.time;
-# 585 "kind.ag"
-
-# 586 "kind.ag"
-      if (imageTime != now &&          // ignore just created image
-# 587 "kind.ag"
-          image.name != lastValidImage // ignore last valid image
-# 588 "kind.ag"
-         )
-# 589 "kind.ag"
-        {
-# 590 "kind.ag"
-          DateTime expireTime;
-# 591 "kind.ag"
-          string expireRule;
-# 592 "kind.ag"
-          if (!image.valid) // invalid image?
-# 593 "kind.ag"
-            {
-# 594 "kind.ag"
-              time_t expPeriod = stot(conf.getString("expireFailedImage"));
-# 595 "kind.ag"
-              if (expPeriod < 0)
-# 596 "kind.ag"
-                throw Exception("expireFailedImage", "Time period must be positive");
-# 597 "kind.ag"
-              expireTime = imageTime + stot(conf.getString("expireFailedImage"));
-# 598 "kind.ag"
-              expireRule = "invalid image: " + conf.getString("expireFailedImage");
-# 599 "kind.ag"
-              debugPrint("- invalid image");
-# 600 "kind.ag"
-            }
-# 601 "kind.ag"
-          else
-# 602 "kind.ag"
-            {
-# 603 "kind.ag"
-              debugPrint("- valid image");
-# 604 "kind.ag"
-              expireTime = image.expire;
-# 605 "kind.ag"
-              expireRule = image.expireRule;
-# 606 "kind.ag"
-            }
-# 607 "kind.ag"
-
-# 608 "kind.ag"
-          if (expireTime < now)
-# 609 "kind.ag"
-            {
-# 610 "kind.ag"
-              if (!quiet)
-# 611 "kind.ag"
-                cout << "  removing image " << image.name << endl;
-# 612 "kind.ag"
-              try
-# 613 "kind.ag"
-                {
-# 614 "kind.ag"
-                  if (removeDir(image.name) != 0)
-# 615 "kind.ag"
-                    cout << "Error removing " <<  image.name << endl;
-# 616 "kind.ag"
-                }
-# 617 "kind.ag"
-              catch (Exception ex)
-# 618 "kind.ag"
-                {
-# 619 "kind.ag"
-                  cerr << "Exception: " << ex.what() << endl;
-# 620 "kind.ag"
-                }
-# 621 "kind.ag"
-            }
-# 622 "kind.ag"
-        }
-# 623 "kind.ag"
-      else
-# 624 "kind.ag"
-        debugPrint("- current image - ignored");
-# 625 "kind.ag"
-    }
-# 626 "kind.ag"
-}
-# 627 "kind.ag"
-
-# 628 "kind.ag"
-/*AppGen:Main*/
-string ag_programName;
-
-void usage()
-{
-  cout << ag_programName << " - archiving backup" << endl;
-  cout << "Usage:" << endl;
-  cout << ag_programName << " [<options>] vault_or_group " << endl;
-  cout << "  vault_or_group - Vault to backup" << endl;
-  cout << "Options:" << endl;
-  cout << "  -c <s>    --masterconfig=<s>" << endl;
-  cout << "     Master config file (default: \"\")" << endl;
-
-  cout << "     if not given or empty kind looks for" << endl;
-  cout << "     /etc/kind/master.conf" << endl;
-  cout << "     /ffp/etc/kind/master.conf" << endl;
-  cout << "  -f        --full=   " << endl;
-  cout << "     Force full image == initial backup (default: false)" << endl;
-
-  cout << "  -B        --backup=   " << endl;
-  cout << "     Backup (default: false)" << endl;
-
-  cout << "  -E        --expire=   " << endl;
-  cout << "     Expire (default: false)" << endl;
-
-  cout << "  -C        --listconfig=   " << endl;
-  cout << "     Show configuration (default: false)" << endl;
-
-  cout << "  -I        --listimages=   " << endl;
-  cout << "     List data of images (default: false)" << endl;
-
-  cout << "     if none of backup, expire, listconfig and listimages is specified," << endl;
-  cout << "     backup and expire is assumed." << endl;
-  cout << "     listconfig and listimages cannot be combined with other actions" << endl;
-  cout << "  -D        --dryrun=   " << endl;
-  cout << "     Dry run (no real backup) (default: false)" << endl;
-
-  cout << "  -F <s>    --forcebackup=<s>" << endl;
-  cout << "     Create image for specified backup set (default: \"\")" << endl;
-
-  cout << "  -v        --verbose=   " << endl;
-  cout << "     Verbose (default: false)" << endl;
-
-  cout << "  -d        --debug=   " << endl;
-  cout << "     Debug output of many data (default: false)" << endl;
-
-  cout << "  -q        --quiet=   " << endl;
-  cout << "     Be quiet - no messages (default: false)" << endl;
-
-  cout << "  -h        --help=   " << endl;
-  cout << "     This help" << endl;
-
-  exit(1);
-}
-
-void error(const string &msg)
-{
-  cout << endl << ag_programName << " - error: " << msg << endl << endl;
-  usage();
-}
-
-int ptoi(const char *para)
-{
-  char *end;
-  int res = strtol(para, &end, 10);
-  if (end == para)
-    error(string("no int: ") + para);
-  if (*end != 0)
-    error(string("garbage in int: ") + para);
-  return res;
-}
-
-double ptod(const char *para)
-{
-  char *end;
-  double res = strtod(para, &end);
-  if (end == para)
-    error(string("no double: ") + para);
-  if (*end != 0)
-    error(string("garbage in double: ") + para);
-  return res;
-}
-
-int main(int argc, char **argv)
-{
-string masterConfig = "";
-bool fullImage = false;
-bool doBackup = false;
-bool doExpire = false;
-bool listConfig = false;
-bool listImages = false;
-string forcedBackupSet = "";
-
-string vault = "";
-  static struct option ag_long_options[] =
-  {
-    {"masterconfig", required_argument, 0, 'c' },
-    {"full", no_argument, 0, 'f' },
-    {"backup", no_argument, 0, 'B' },
-    {"expire", no_argument, 0, 'E' },
-    {"listconfig", no_argument, 0, 'C' },
-    {"listimages", no_argument, 0, 'I' },
-    {"dryrun", no_argument, 0, 'D' },
-    {"forcebackup", required_argument, 0, 'F' },
-    {"verbose", no_argument, 0, 'v' },
-    {"debug", no_argument, 0, 'd' },
-    {"quiet", no_argument, 0, 'q' },
-    {"help", no_argument, 0, 'h' },
-    {0,         0,                 0,  0 }
-  };
-  ag_programName = argv[0];
-  int rc;
-  opterr = 0;
-  while ((rc = getopt_long(argc, argv, ":c:fBECIDF:vdqh", ag_long_options, NULL)) >= 0)
-    {
-      switch (rc)
-        {
-        case '?':
-          error("Unknown option");
-          break;
-        case ':':
-          error("Expecting option parameter");
-          break;
-        case 'c':
-              masterConfig = optarg;
-              break;
-
-        case 'f':
-              fullImage = true;
-              break;
-
-        case 'B':
-              doBackup = true;
-              break;
-
-        case 'E':
-              doExpire = true;
-              break;
-
-        case 'C':
-              listConfig = true;
-              break;
-
-        case 'I':
-              listImages = true;
-              break;
-
-        case 'D':
-              dryRun = true;
-              break;
-
-        case 'F':
-              forcedBackupSet = optarg;
-              break;
-
-        case 'v':
-              verbose = true;
-              break;
-
-        case 'd':
-              debug = true;
-              break;
-
-        case 'q':
-              quiet = true;
-              break;
-
-        case 'h':
-              usage();
-              break;
-
-        default:
-          error("error in options");
-        }
-    }
-  if (optind < argc)
-    vault = argv[optind++];
-  else error("Parameter vault_or_group needed");
-
-/*AppGen:MainEnd*/
-# 632 "kind.ag"
-
-# 633 "kind.ag"
-  int exitCode = 0;
-# 634 "kind.ag"
-  string lockFile;
-# 635 "kind.ag"
-  try
-# 636 "kind.ag"
-    {
-# 637 "kind.ag"
-      // handling of parameters and switches
-# 638 "kind.ag"
-      if (debug)        // debug implies verbose
-# 639 "kind.ag"
-        verbose = true;
-# 640 "kind.ag"
-
-# 641 "kind.ag"
-      if (!doBackup && !doExpire && !listConfig && !listImages)
-# 642 "kind.ag"
-        {
-# 643 "kind.ag"
-          doBackup = true;
-# 644 "kind.ag"
-          doExpire = true;
-# 645 "kind.ag"
-        }
-# 646 "kind.ag"
-
-# 647 "kind.ag"
-      KindConfig conf;
-# 648 "kind.ag"
-
-# 649 "kind.ag"
-      // default-values
-# 650 "kind.ag"
-      conf.add("imageName", "image");
-# 651 "kind.ag"
-      conf.add("vaultConfigName", "kind/vault.conf");
-# 652 "kind.ag"
-      conf.add("expireFailedImage", "3 days");
-# 653 "kind.ag"
-      conf.add("expireRule", "* * * * 1 month");
-# 654 "kind.ag"
-      conf.add("ping", "ping -c 1 -W 5 %host");
-# 655 "kind.ag"
-      conf.add("rsyncOption", ""); // no additional rsync option
-# 656 "kind.ag"
-      conf.add("remoteShell", "");
-# 657 "kind.ag"
-      conf.add("lockfile", "/var/lock/kind");
-# 658 "kind.ag"
-      conf.add("userExcludeFile", "nobackup.list");
-# 659 "kind.ag"
-      conf.add("userExcludeCommand",
-# 660 "kind.ag"
-               "find %path -type f -iname '*nobackup' -printf '%P\\\\n'");
-# 661 "kind.ag"
-      conf.add("logSize", "");
-# 662 "kind.ag"
-      conf.add("lastLink", "symLink");
-# 663 "kind.ag"
-
-# 664 "kind.ag"
-      if (listConfig)
-# 665 "kind.ag"
-        {
-# 666 "kind.ag"
-          cout << "builtin config" << endl;
-# 667 "kind.ag"
-          conf.print(".   ");
-# 668 "kind.ag"
-        }
-# 669 "kind.ag"
-
-# 670 "kind.ag"
-      readMasterConfig(masterConfig, conf);
-# 671 "kind.ag"
-
-# 672 "kind.ag"
-      banks = conf.getStrings("bank");
-# 673 "kind.ag"
-      if (banks.empty())
-# 674 "kind.ag"
-        throw Exception("read master configuration", "no banks defined");
-# 675 "kind.ag"
-
-# 676 "kind.ag"
-      if (listConfig)
-# 677 "kind.ag"
-        {
-# 678 "kind.ag"
-          cout << "global config:" << endl;
-# 679 "kind.ag"
-          conf.print(".   ");
-# 680 "kind.ag"
-          readVaultConfig(vault, conf);
-# 681 "kind.ag"
-          cout << "vault config:" << endl;
-# 682 "kind.ag"
-          conf.print(".   ");
-# 683 "kind.ag"
-          exit(0);
-# 684 "kind.ag"
-        }
-# 685 "kind.ag"
-
-# 686 "kind.ag"
-      DateTime imageTime = DateTime::now();
-# 687 "kind.ag"
-
-# 688 "kind.ag"
-      if (listImages)
-# 689 "kind.ag"
-        {
-# 690 "kind.ag"
-          listImageInfo(vault, conf, imageTime, forcedBackupSet);
-# 691 "kind.ag"
-          exit(0);
-# 692 "kind.ag"
-        }
-# 693 "kind.ag"
-
-# 694 "kind.ag"
-      lockFile = conf.getString("lockfile");
-# 695 "kind.ag"
-      createLock(lockFile);
-# 696 "kind.ag"
-
-# 697 "kind.ag"
-      string logSizeFile = conf.getString("logSize");
-# 698 "kind.ag"
-      readSizes(logSizeFile);
-# 699 "kind.ag"
-
-# 700 "kind.ag"
-      vector<string> vaults;
-# 701 "kind.ag"
-      string groupname = "group_" + vault;
-# 702 "kind.ag"
-      if (conf.hasKey(groupname))
-# 703 "kind.ag"
-        vaults = conf.getStrings(groupname);
-# 704 "kind.ag"
-      else
-# 705 "kind.ag"
-        vaults.push_back(vault);
-# 706 "kind.ag"
-
-# 707 "kind.ag"
-
-# 708 "kind.ag"
-      for (string vault : vaults)
-# 709 "kind.ag"
-        {
-# 710 "kind.ag"
-          if (doBackup)
-# 711 "kind.ag"
-            if (backupVault(vault, conf, imageTime, fullImage, forcedBackupSet))
-# 712 "kind.ag"
-              writeSizes(logSizeFile);
-# 713 "kind.ag"
-          if (doExpire)
-# 714 "kind.ag"
-            expireVault(vault, conf, imageTime);
-# 715 "kind.ag"
-        }
-# 716 "kind.ag"
-
-# 717 "kind.ag"
-      if (!quiet)
-# 718 "kind.ag"
-        cout << DateTime::now().getString('h') << ": finished" << endl;
-# 719 "kind.ag"
-
-# 720 "kind.ag"
-    }
-# 721 "kind.ag"
-  catch (const Exception& ex)
-# 722 "kind.ag"
-    {
-# 723 "kind.ag"
-      cerr << "Exception: " << ex.what() << endl;
-# 724 "kind.ag"
-      exitCode = 1;
-# 725 "kind.ag"
-    }
-# 726 "kind.ag"
-  catch (const char* msg)
-# 727 "kind.ag"
-    {
-# 728 "kind.ag"
-      cerr << "Exception(char*): " << msg << endl;
-# 729 "kind.ag"
-      exitCode = 1;
-# 730 "kind.ag"
-    }
-# 731 "kind.ag"
-  catch (const string& msg)
-# 732 "kind.ag"
-    {
-# 733 "kind.ag"
-      cerr << "Exception(string): " << msg << endl;
-# 734 "kind.ag"
-      exitCode = 1;
-# 735 "kind.ag"
-    }
-# 736 "kind.ag"
-  removeLock(lockFile);
-# 737 "kind.ag"
-  return exitCode;
-# 738 "kind.ag"
-}

+ 15 - 5
src/stringtools.cpp

@@ -225,13 +225,23 @@ string timeString(time_t t)
   int hour = t % 24;
   int hour = t % 24;
   t /= 24;
   t /= 24;
   int days = t;
   int days = t;
+  int nParts = 0;
   if (days > 0)
   if (days > 0)
-    res += to_string(days) + " days ";
+    {
+      res += to_string(days) + " days ";
+      nParts++;
+    }
   if (hour > 0)
   if (hour > 0)
-    res += to_string(hour) + " hours ";
-  if (min > 0)
-    res += to_string(min) + " minutes ";
-  if (sec > 0 || res.empty())
+    {
+      res += to_string(hour) + " hours ";
+      nParts++;
+    }
+  if (min > 0 && nParts < 2)
+    {
+      res += to_string(min) + " minutes ";
+      nParts++;
+    }
+  if ((sec > 0 && nParts < 2) || nParts == 0)
     res += to_string(sec) + " seconds ";
     res += to_string(sec) + " seconds ";
   return res;
   return res;
 }
 }