Browse Source

initial upload

OWeh55 9 years ago
parent
commit
230e932dec
19 changed files with 3932 additions and 0 deletions
  1. 92 0
      DateTime.cpp
  2. 88 0
      DateTime.h
  3. 30 0
      Exception.h
  4. 144 0
      FileName.cpp
  5. 42 0
      FileName.h
  6. 142 0
      KindConfig.cpp
  7. 41 0
      KindConfig.h
  8. 137 0
      Lexer.cpp
  9. 85 0
      Lexer.h
  10. 49 0
      Makefile
  11. 23 0
      Strings.h
  12. 217 0
      filetools.cpp
  13. 37 0
      filetools.h
  14. 770 0
      kind.ag
  15. 1673 0
      kind.cpp
  16. 117 0
      rulecomp.cpp
  17. 12 0
      rulecomp.h
  18. 195 0
      stringtools.cpp
  19. 38 0
      stringtools.h

+ 92 - 0
DateTime.cpp

@@ -0,0 +1,92 @@
+#include <vector>
+
+#include "stringtools.h"
+#include "Exception.h"
+
+#include "DateTime.h"
+
+using namespace std;
+
+void DateTime::get(int& year, int& month, int& mday,
+                   int& hour, int& min, int& sec) const
+{
+  tm* ltm = localtime(&theTime);
+  year = 1900 + ltm->tm_year;
+  month = 1 + ltm->tm_mon;
+  mday = ltm->tm_mday;
+  hour =  ltm->tm_hour;
+  min = ltm->tm_min;
+  sec = ltm->tm_sec;
+}
+
+DateTime::DateTime(int Y, int M, int D, int h, int m, int s)
+{
+  struct tm ltm;
+  ltm.tm_year = Y - 1900;
+  ltm.tm_mon = M - 1;
+  ltm.tm_mday = D;
+  ltm.tm_min = m;
+  ltm.tm_hour = h;
+  ltm.tm_sec = s;
+  ltm.tm_isdst = -1;
+  theTime = mktime(&ltm);
+}
+
+#if 0
+struct tm
+{
+  int tm_sec;    /* Seconds (0-60) */
+  int tm_min;    /* Minutes (0-59) */
+  int tm_hour;   /* Hours (0-23) */
+  int tm_mday;   /* Day of the month (1-31) */
+  int tm_mon;    /* Month (0-11) */
+  int tm_year;   /* Year - 1900 */
+  int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
+  int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
+  int tm_isdst;  /* Daylight saving time */
+};
+#endif
+
+bool DateTime::match(const set<int>& Y, const set<int>& M, const set<int>& D,
+                     const set<int>& W,
+                     const set<int>& h, const set<int>& m, const set<int>& s)
+{
+  tm* tm = localtime(&theTime);
+  return match(Y, tm->tm_year) &&
+         match(M, tm->tm_mon - 1) &&
+         match(D, tm->tm_mday) &&
+         match(W, tm->tm_wday) &&
+         match(h, tm->tm_hour) &&
+         match(m, tm->tm_min) &&
+         match(s, tm->tm_sec);
+}
+
+using std::to_string;
+
+std::string DateTime::toString2(int v)
+{
+  std::string res = to_string(v);
+  if (res.size() < 2)
+    res = "0" + res;
+  return res;
+}
+
+std::string DateTime::getString(char typ) const
+{
+  int y, mo, d, h, mi, s;
+  get(y, mo, d, h, mi, s);
+  switch (typ)
+    {
+    case 'h': // human (german)
+      return to_string(d) + "." + to_string(mo) + "." + to_string(y) + "  " +
+             toString2(h) + ':' + toString2(mi) + ':' + toString2(s);
+    case 'm': // machine
+      return toString2(y) + "-" + toString2(mo) + "-" + toString2(d) + "-" +
+             toString2(h) + '-' + toString2(mi) + '-' + toString2(s);
+    case 's': // short machine
+      return toString2(y) + "-" + toString2(mo) + "-" + toString2(d) + "-" +
+             toString2(h);
+      std::to_string(h);
+    }
+  throw Exception("DateTime", "wrong string format type");
+}

+ 88 - 0
DateTime.h

@@ -0,0 +1,88 @@
+#ifndef DATE_TIME_H
+#define DATE_TIME_H
+
+#include <set>
+#include <string>
+#include <time.h>
+
+class DateTime
+{
+public:
+  static DateTime now()
+  {
+    return DateTime(time(NULL));
+  }
+
+  explicit DateTime(time_t t): theTime(t) {}
+
+  DateTime(int Y, int M, int D, int h, int m, int s);
+
+  friend DateTime operator+(const DateTime& t1, time_t t2)
+  {
+    return DateTime(t1.theTime + t2);
+  }
+
+  const DateTime& operator+=(time_t t2)
+  {
+    theTime += t2;
+    return *this;
+  }
+
+  void get(int& year, int& month, int& mday,
+           int& hour, int& min, int& sec) const;
+
+  std::string getString(char typ = 'h') const;
+
+  const DateTime& operator-=(time_t t2)
+  {
+    theTime -= t2;
+    return *this;
+  }
+
+  friend DateTime operator-(const DateTime& t1, time_t t2)
+  {
+    return DateTime(t1.theTime - t2);
+  }
+
+  friend bool operator<(const DateTime& t1, const DateTime& t2)
+  {
+    return t1.theTime < t2.theTime;
+  }
+
+  friend bool operator<=(const DateTime& t1, const DateTime& t2)
+  {
+    return t1.theTime <= t2.theTime;
+  }
+  friend bool operator==(const DateTime& t1, const DateTime& t2)
+  {
+    return t1.theTime == t2.theTime;
+  }
+  friend bool operator!=(const DateTime& t1, const DateTime& t2)
+  {
+    return t1.theTime != t2.theTime;
+  }
+
+  friend bool operator>(const DateTime& t1, const DateTime& t2)
+  {
+    return t1.theTime > t2.theTime;
+  }
+
+  friend bool operator>=(const DateTime& t1, const DateTime& t2)
+  {
+    return t1.theTime >= t2.theTime;
+  }
+
+  bool match(const std::set<int>& Y, const std::set<int>& M, const std::set<int>& D,
+             const std::set<int>& W,
+             const std::set<int>& h, const std::set<int>& m, const std::set<int>& s);
+
+private:
+  static bool match(const std::set<int>& v, int v2)
+  {
+    return (v.empty()) || (v.count(v2) > 0);
+  }
+
+  static std::string toString2(int v);
+  time_t theTime;
+};
+#endif

+ 30 - 0
Exception.h

@@ -0,0 +1,30 @@
+#ifndef IM_EXCEPTIONS_H
+#define IM_EXCEPTIONS_H
+#include <string>
+#include <exception>
+
+class Exception: public std::exception
+{
+protected:
+  std::string where;
+  std::string msg;
+  mutable std::string fullMessage;
+public:
+  Exception(): where("unknown"), msg("unknown") {}
+  Exception(const std::string& where, const std::string& msg):
+    where(where), msg(msg) {}
+
+  virtual void setWhere(const std::string& w)
+  {
+    where = w;
+  }
+
+  virtual const char* what() const noexcept
+  {
+    fullMessage = where + " - " + msg;
+    return fullMessage.c_str();
+  }
+
+  virtual ~Exception() throw() {}
+};
+#endif

+ 144 - 0
FileName.cpp

@@ -0,0 +1,144 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <iostream>
+
+#include "Exception.h"
+#include "FileName.h"
+
+using namespace std;
+
+void FileName::setPath(const std::string& n)
+{
+  if (n.size() == 0)
+    throw Exception("FileName::setPath", "empty pathname");
+  absolute = n[0] == pathdel;
+  path.clear();
+  unsigned int i = 0;
+  string thisPart;
+  while (i < n.size())
+    {
+      if (n[i] != pathdel)
+        thisPart += n[i];
+      else
+        {
+          if (!thisPart.empty())
+            path.push_back(thisPart);
+          thisPart.clear();
+        }
+      ++i;
+    }
+  if (!thisPart.empty())
+    path.push_back(thisPart);
+}
+
+FileName::FileName(const std::string& n)
+{
+  if (n.size() == 0)
+    throw Exception("FileName", "empty filename");
+
+  setPath(n);
+
+  // last path component is name
+  if (path.size() > 0)
+    {
+      name = path.back();
+      path.pop_back();
+
+      size_t lastExtensionDelimiterPosition = name.rfind(".");
+      if (lastExtensionDelimiterPosition == std::string::npos)
+        extension = "";
+      else
+        {
+          extension = name.substr(lastExtensionDelimiterPosition + 1);
+          name.resize(lastExtensionDelimiterPosition);
+        }
+    }
+}
+
+void FileName::setName(const string& n)
+{
+  for (unsigned int i = 0; i < n.size(); ++i)
+    if (n[i] == pathdel)
+      throw Exception("Filename", "path delimiter in name");
+  name = n;
+}
+
+void FileName::setExtension(const string& n)
+{
+  for (unsigned int i = 0; i < n.size(); ++i)
+    {
+      if (n[i] == extdel)
+        throw Exception("Filename", "extension delimiter in extension");
+      if (n[i] == pathdel)
+        throw Exception("Filename", "path delimiter in extension");
+    }
+  extension = n;
+}
+
+FileName::FileName(const std::string& path,
+                   const std::string& name,
+                   const std::string& ext)
+{
+  setPath(path);
+  setName(name);
+  setExtension(ext);
+}
+
+std::string FileName::getFileName() const
+{
+  std::string res = getPath();
+  if (res != "/")
+    res += '/';
+  res += name;
+  if (!extension.empty())
+    res += extdel + extension;
+  return res;
+}
+
+std::string FileName::getPath() const
+{
+  string res;
+  if (absolute)
+    res += '/';
+  if (path.size() > 0)
+    {
+      for (unsigned int i = 0; i < path.size() - 1; ++i)
+        res += path[i] + '/';
+      res += path.back();
+    }
+  return res;
+}
+
+#if 0
+using namespace std;
+
+int main(int argc, char** argv)
+{
+  try
+    {
+      if (argc == 2)
+        {
+          FileName fn(argv[1]);
+          cout << fn.getPath() << endl;
+          cout << fn.getName() << endl;
+          cout << fn.getExtension() << endl;
+          cout << fn.getFileName() << endl;
+        }
+      else if (argc == 4)
+        {
+          FileName fn(argv[1], argv[2], argv[3]);
+          cout << fn.getPath() << endl;
+          cout << fn.getName() << endl;
+          cout << fn.getExtension() << endl;
+          cout << fn.getFileName() << endl;
+        }
+    }
+  catch (const char* msg)
+    {
+      cout << "exception: " << msg << endl;
+    }
+}
+#endif
+

+ 42 - 0
FileName.h

@@ -0,0 +1,42 @@
+#ifndef _FILENAME_H
+#define _FILENAME_H
+
+#include <vector>
+#include <string>
+
+class FileName
+{
+public:
+  static const char pathdel = '/';
+  static const char extdel = '.';
+
+  FileName(): absolute(false) {};
+  FileName(const std::string& filename);
+  FileName(const std::string& path,
+           const std::string& name,
+           const std::string& ext);
+
+  std::string getFileName() const;
+
+  std::string getName() const
+  {
+    return name;
+  }
+  std::string getPath() const;
+  std::string getExtension() const
+  {
+    return extension;
+  }
+
+  void setName(const std::string& n);
+  void setPath(const std::string& p);
+  void setExtension(const std::string& x);
+
+private:
+  bool absolute;
+  std::vector<std::string> path;
+  std::string name;
+  std::string extension;
+};
+
+#endif

+ 142 - 0
KindConfig.cpp

@@ -0,0 +1,142 @@
+#include <iostream>
+
+#include "Exception.h"
+#include "KindConfig.h"
+
+using namespace std;
+
+KindConfig::KindConfig(const std::string& fn)
+{
+  // settings.clear();
+  addFile(fn);
+}
+
+KindConfig::KindConfig(std::istream& is)
+{
+  // settings.clear();
+  addFile(is);
+}
+
+void KindConfig::addFile(std::istream& is)
+{
+  string line;
+  string lastkey;
+  while (getline(is, line))
+    {
+      line = trim(line);
+      if (!line.empty() && line[0] != '#')
+        {
+          string key, value;
+          string del;
+          split(line, key, del, value);
+          if (key.empty())
+            key = lastkey;
+          if (key.empty())
+            throw Exception("read config", "empty key");
+
+          if (del == "=")
+            settings[key].clear();
+
+          settings[key].push_back(value);
+          lastkey = key;
+        }
+    }
+}
+
+void KindConfig::addFile(const std::string& fn)
+{
+  ifstream is(fn);
+  if (!is.good())
+    throw Exception("read config", string("Cannot open ") + fn + " for reading");
+  addFile(is);
+}
+
+bool KindConfig::hasKey(std::string key) const
+{
+  key = trim(key);
+  auto it = settings.find(key);
+  return it != settings.end();
+}
+
+std::string KindConfig::getString(std::string key) const
+{
+  key = trim(key);
+  auto it = settings.find(key);
+  if (it == settings.end())
+    throw Exception("get config key", std::string("No key \"") + key + "\"");
+  if (it->second.size() != 1)
+    throw Exception("get config key", std::string("Key \"") + key + "\" is no single value");
+  return it->second[0];
+}
+
+Strings KindConfig::getStrings(std::string key) const
+{
+  key = trim(key);
+  auto it = settings.find(key);
+  if (it == settings.end())
+    throw Exception("get config key", std::string("No key \"") + key + "\"");
+  return it->second;
+}
+
+bool KindConfig::getBool(std::string key) const
+{
+  key = trim(key);
+  auto it = settings.find(key);
+  if (it == settings.end())
+    return false;
+  if (it->second.size() != 1)
+    throw Exception("get config key", std::string("Key \"") + key + "\" is no single value");
+  std::string val = it->second[0];
+  if (val.empty() || val == "true")
+    return true;
+  if (val != "false")
+    throw Exception("get boolean key " + key, std::string("Invalid value \"") + val + "\"");
+  return false;
+}
+
+void KindConfig::print() const
+{
+  for (auto it = settings.begin(); it != settings.end(); ++it)
+    {
+      cout << it->first << ": ";
+      if (it->second.size() > 1)
+        for (string v : it->second)
+          cout << v << "|";
+      else if (!it->second.empty())
+        cout << it->second[0];
+      cout << endl;
+    }
+}
+
+void KindConfig::split(const string& line,
+                       string& key,
+                       string& del,
+                       string& value)
+{
+  unsigned int i = 0;
+  key.clear();
+  del.clear();
+  value.clear();
+  while (i < line.size() && line[i] != '+' && line[i] != '=')
+    {
+      key += line[i];
+      ++i;
+    }
+
+  key = trim(key);
+  while (i < line.size() && (line[i] == '+' || line[i] == '='))
+    {
+      del += line[i];
+      ++i;
+    }
+
+  if (del != "=" && del != "+=")
+    throw Exception("config", string("wrong delimiter ") + del + " in " + line);
+
+  while (i < line.size())
+    {
+      value += line[i];
+      ++i;
+    }
+  value = trim(value);
+}

+ 41 - 0
KindConfig.h

@@ -0,0 +1,41 @@
+#ifndef Kind_CONFIG_H
+#define Kind_CONFIG_H
+
+#include <fstream>
+#include <map>
+#include <string>
+#include "Strings.h"
+#include "stringtools.h"
+
+class KindConfig
+{
+public:
+  KindConfig() {}
+  KindConfig(const std::string& fn);
+  KindConfig(std::istream& is);
+
+  void addFile(const std::string& fn);
+  void addFile(std::istream& is);
+  void add(const std::string& key, const std::string& value)
+  {
+    settings[key].push_back(value);
+  }
+
+  bool hasKey(std::string key) const;
+
+  std::string getString(std::string key) const;
+  Strings getStrings(std::string key) const;
+
+  bool getBool(std::string key) const;
+
+  void print() const;
+private:
+  void split(const std::string& line,
+             std::string& key,
+             std::string& del,
+             std::string& value);
+
+  std::map<std::string, Strings> settings;
+};
+
+#endif

+ 137 - 0
Lexer.cpp

@@ -0,0 +1,137 @@
+#include <ctype.h>
+
+#include <iostream>
+
+#include "Lexer.h"
+
+using namespace std;
+
+char Lexer::nextChar() const
+{
+  if (pos < str.size())
+    return str[pos];
+  return 0;
+}
+
+char Lexer::getChar()
+{
+  if (pos < str.size())
+    return str[pos++];
+  return 0;
+}
+
+void Lexer::skipChar()
+{
+  pos++;
+}
+
+void Lexer::skipWhiteSpace()
+{
+  if (pos < str.size() && isblank(str[pos]))
+    pos++;
+}
+
+void Lexer::nextToken()
+{
+  token.clear();
+  type = nothing;
+  skipWhiteSpace();
+  char f = getChar();
+  if (f != 0)
+    {
+      token += f;
+      if (isdigit(f))
+        {
+          // token is number
+          type = integer; // assume "integer"
+          // number
+          f = nextChar();
+          while (isdigit(f) || f == '.')
+            {
+              if (f == '.')
+                {
+                  type = floatingpoint;  // -> floating point
+                }
+              token += f;
+              skipChar();
+              f = nextChar();
+            }
+        }
+      else if (f == '"')
+        {
+          // string literal starting with '"'
+          char delimiter = f;
+          token = "\"";
+          type = stringliteral;
+
+          f = nextChar();
+          while (f != delimiter)
+            {
+              if (f == 0)
+                throw Exception("Parsing", "string literal not complete");
+              token += f;
+              skipChar();
+              f = nextChar();
+            }
+          token += '"';
+          skipChar();
+        }
+      else if (isalpha(f))
+        {
+          //    cout << "id"<<endl;
+          // identifier
+          type = identifier;
+          f = nextChar();
+          //    cout << (int)f << " " << f << endl;
+          while (isalnum(f))
+            {
+              token += f;
+              skipChar();
+              f = nextChar();
+            }
+        }
+      else
+        {
+          type = singlecharacter;
+          // single character
+        }
+    }
+  skipWhiteSpace();
+}
+
+long int Lexer::getInt()
+{
+  // cout << "type: " << type << " token:" << token << endl;
+  if (type != integer)
+    throw Exception("getInt", "integer value expected");
+  int res = stol(token);
+  nextToken();
+  return res;
+}
+
+double Lexer::getDouble()
+{
+  if (type != floatingpoint)
+    throw Exception("getDouble", "floating point value expected");
+  double res = stod(token);
+  nextToken();
+  return res;
+}
+
+std::string Lexer::getString()
+{
+  if (type != stringliteral)
+    throw Exception("getString", "string expected");
+  string res = token.substr(1, token.length() - 2);
+  nextToken();
+  return res;
+}
+
+std::string Lexer::getWord()
+{
+  if (type != identifier)
+    throw Exception("getString", "identifier or keyword expected");
+  string res = token;
+  nextToken();
+  return res;
+}

+ 85 - 0
Lexer.h

@@ -0,0 +1,85 @@
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <stdexcept>
+
+#include "Exception.h"
+
+#include <ctype.h>
+#include <string>
+
+class Lexer
+{
+public:
+  static const int nothing = 0;
+  static const int integer = 1;
+  static const int floatingpoint = 2;
+  static const int identifier = 4;
+  static const int stringliteral = 8;
+  static const int singlecharacter = 16;
+
+  Lexer(const std::string& s, int start = 0):
+    str(s), pos(start)
+  {
+    nextToken();
+  }
+
+  // all handled
+  virtual bool empty() const
+  {
+    return token.empty() && pos >= str.size();
+  }
+
+  // get next token
+  virtual void nextToken();
+
+  virtual std::string getAll()
+  {
+    std::string res = str.substr(pos, str.size() - pos);
+    pos = str.size();
+    return res;
+  }
+
+  virtual long int getInt();
+  virtual double getDouble();
+  virtual std::string getString();
+  virtual std::string getWord();
+
+  // expect and remove specific token (string)
+  virtual void expect(const std::string& tok)
+  {
+    if (token != tok)
+      throw Exception("Parsing", "Expected " + tok);
+    nextToken();
+  }
+
+  // expect and remove specific token (singlecharacter)
+  virtual void expect(char tok)
+  {
+    if (type != singlecharacter || token[0] != tok)
+      throw Exception("Parsing", std::string("Expected ") + tok);
+    nextToken();
+  }
+
+  virtual std::string getString() const
+  {
+    return str;
+  }
+
+  std::string token;
+  int type;
+
+private:
+  virtual char nextChar() const;
+  virtual char getChar();
+  virtual void skipChar();
+  virtual void skipWhiteSpace();
+
+  //  virtual void get_token();
+
+  std::string str;
+
+  unsigned int pos;
+};
+
+#endif

+ 49 - 0
Makefile

@@ -0,0 +1,49 @@
+MAIN=kind
+
+#CXX=clang++
+
+OBJECTS=$(MAIN).o filetools.o FileName.o DateTime.o stringtools.o KindConfig.o Lexer.o rulecomp.o
+
+LOPT := $(OPT)
+COPT := $(OPT) -std=c++11
+LIBS := 
+
+COPT := $(COPT) -g -Wall  -fno-strict-aliasing 
+
+# source code beautifier
+ASTYLE := astyle
+# format options for beautifier
+ASTYLE_OPT = --style=gnu --unpad-paren --pad-header --pad-oper --indent-namespaces --indent=spaces=2 --convert-tabs --align-pointer=type --align-reference=type --remove-brackets
+#--add-brackets
+
+%.o:%.c
+	$(CC) $(COPT) $(INCLUDE) -c $*.c
+
+%.o:%.cpp
+	$(CXX) $(COPT) $(INCLUDE) -c $*.cpp
+
+%: %.o
+	$(CXX) $(COPT) $(LOPT) $(LIBRARY) -o $* $*.o $(LIBS)
+
+%:%.cpp
+	$(CXX) $(COPT) $(INCLUDE) -c $*.cpp
+	$(CXX) $(LIBRARY) $(LOPT) -o $* $*.o $(LIBS)
+
+%.cpp:%.ag
+	AppGen $*.ag $*.cpp
+
+$(MAIN): dep $(OBJECTS)
+	$(CXX) $(LOPT) $(LIBRARY) -o $(MAIN) $(OBJECTS) $(LIBS)
+
+clean:
+	-rm -f *.o depend *~ *orig
+
+dep:	$(MAIN).cpp
+	-rm depend
+	touch depend
+	$(CXX) -M $(COPT) $(INCLUDE) *.cpp >> depend	
+
+format:
+	$(ASTYLE) $(ASTYLE_OPT) *.cpp *.ag *.h
+
+-include depend

+ 23 - 0
Strings.h

@@ -0,0 +1,23 @@
+#ifndef STRINGS_H
+#define STRINGS_H
+
+#include <vector>
+
+class Strings: public std::vector<std::string>
+{
+public:
+  Strings operator+=(const Strings& s2)
+  {
+    for (const std::string& s : s2)
+      push_back(s);
+    return *this;
+  }
+  Strings operator+=(const std::string& s)
+  {
+    push_back(s);
+    return *this;
+  }
+};
+
+
+#endif

+ 217 - 0
filetools.cpp

@@ -0,0 +1,217 @@
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <cstring>
+#include <iostream>
+#include <fstream>
+
+#include "Exception.h"
+
+#include "filetools.h"
+
+std::string concatPathAndFilename(const std::string& path, const std::string& filename)
+{
+  std::string result = path;
+  if (result[result.length() - 1] != '/')
+    result += "/";
+  result += filename;
+  return result;
+}
+
+void fileList(const std::string& fn,
+              std::vector<std::string>& file, bool recursive)
+{
+  struct stat fstat;
+  if (stat(fn.c_str(), &fstat) != 0)
+    return;
+  if (S_ISREG(fstat.st_mode))
+    file.push_back(fn);
+  else if (S_ISDIR(fstat.st_mode))
+    {
+      struct dirent*  dp;
+      DIR* dir;
+      if ((dir = opendir(fn.c_str())) != NULL)
+        {
+          /* Loop through directory entries. */
+          while ((dp = readdir(dir)) != NULL)
+            {
+              std::string fname = dp->d_name;
+              std::string pname = concatPathAndFilename(fn, fname);
+              if (stat(pname.c_str(), &fstat) == 0)
+                {
+                  if (S_ISDIR(fstat.st_mode))
+                    {
+                      if (recursive)
+                        if (fname != "." && fname != "..")
+                          fileList(pname, file, true);
+                    }
+                  else if (S_ISREG(fstat.st_mode))
+                    file.push_back(pname);
+                }
+            }
+          closedir(dir);
+        }
+    }
+}
+
+void dirList(const std::string& fn,
+             std::vector<std::string>& file, bool recursive)
+{
+  struct stat fstat;
+  if (stat(fn.c_str(), &fstat) != 0)
+    return;
+  if (S_ISREG(fstat.st_mode))
+    file.push_back(fn);
+  else if (S_ISDIR(fstat.st_mode))
+    {
+      struct dirent*  dp;
+      DIR* dir;
+      if ((dir = opendir(fn.c_str())) != NULL)
+        {
+          /* Loop through directory entries. */
+          while ((dp = readdir(dir)) != NULL)
+            {
+              std::string fname = dp->d_name;
+              std::string pname = concatPathAndFilename(fn, fname);
+              if (stat(pname.c_str(), &fstat) == 0)
+                {
+                  if (S_ISDIR(fstat.st_mode))
+                    {
+                      if (fname != "." && fname != "..")
+                        {
+                          file.push_back(pname);
+                          if (recursive)
+                            dirList(pname, file, true);
+                        }
+                    }
+                }
+            }
+          closedir(dir);
+        }
+    }
+}
+
+void fileList(int argc, char** argv,
+              int optind, std::vector<std::string>& file, bool recursive)
+{
+  for (int i = optind; i < argc; i++)
+    fileList(argv[i], file, recursive);
+}
+
+void strings2File(const std::vector<std::string>& s, const std::string& fn)
+{
+  std::ofstream os(fn);
+  if (!os.good())
+    throw Exception("strings to file", "Cannot open file " + fn);
+  for (std::string t : s)
+    os << t << std::endl;
+  os.close();
+}
+
+void file2Strings(const std::string& fn, std::vector<std::string>& s)
+{
+  std::ifstream is(fn);
+  if (!is.good())
+    throw Exception("file to strings", "Cannot open file " + fn);
+  std::string input;
+  while (getline(is, input))
+    s.push_back(input);
+}
+
+bool fileExists(const std::string& name)
+{
+  struct stat fstat;
+  if (stat(name.c_str(), &fstat) != 0)
+    return false;
+  if (!S_ISREG(fstat.st_mode))
+    return false;
+  return true;
+}
+
+bool dirExists(const std::string& name)
+{
+  struct stat fstat;
+  if (stat(name.c_str(), &fstat) != 0)
+    return false;
+  if (!S_ISDIR(fstat.st_mode))
+    return false;
+  return true;
+}
+
+int lock_fd;
+
+void createLock(const std::string& lockfilename)
+{
+  lock_fd = open(lockfilename.c_str(), O_RDWR | O_CREAT, 0666); // open or create lockfile
+  //check open success...
+  int rc = flock(lock_fd, LOCK_EX | LOCK_NB); // grab exclusive lock, fail if can't obtain.
+  if (rc)
+    {
+      std::cerr << "ERROR: cannot get lock at " + lockfilename << std::endl;
+      exit(1);
+      // no exception, because at normal end of program lock is
+      // unlocked and removed
+    }
+}
+
+void removeLock(const std::string& lockfilename)
+{
+  flock(lock_fd, LOCK_UN);
+  unlink(lockfilename.c_str());
+}
+
+#define POPEN_BUFFER_SIZE 2000
+Strings myPopen(const std::string& cmd,
+                int& rc, bool debug, const std::string& logfn)
+{
+  if (debug)
+    std::cout << "Executing " << cmd << std::endl;
+  std::ofstream log;
+  if (!logfn.empty())
+    log.open(logfn);
+
+  if (log.is_open())
+    {
+      log << "Executing " << cmd << std::endl;
+      log << "--------------------------------------------------------" << std::endl;
+    }
+  Strings res;
+  FILE* fd = popen(cmd.c_str(), "r");
+  if (fd != nullptr)
+    {
+      std::string input;
+      char buffer[POPEN_BUFFER_SIZE];
+      while (fgets(buffer, POPEN_BUFFER_SIZE - 1, fd))
+        {
+          buffer[POPEN_BUFFER_SIZE - 1] = 0; // force string end here
+          int size = strlen(buffer);
+          if (buffer[size - 1] == '\n') // substitute linefeed with string end
+            buffer[size - 1] = 0;
+          input = buffer;
+          if (log.is_open())
+            log << input << std::endl;
+          res.push_back(input);
+          if (debug)
+            std::cout << ">>" << input << "<<" << std::endl;
+        }
+      rc = pclose(fd);
+      if (debug)
+        std::cout << "    result code: " << rc << std::endl;
+    }
+  else
+    {
+      if (debug)
+        std::cout << "    popen " << cmd  << " failed " << std::endl;
+      rc = 1;
+      return res;
+    }
+
+  if (log.is_open())
+    {
+      log << "--------------------------------------------------------" << std::endl;
+      log << "result code: " << rc << std::endl;
+    }
+  return res;
+}

+ 37 - 0
filetools.h

@@ -0,0 +1,37 @@
+#ifndef FILE_TOOLS_H
+#define FILE_TOOLS_H
+
+#include <string>
+#include <vector>
+#include "Strings.h"
+
+// collect files matching fn
+// if "recursive" also inspect subdirectories
+void fileList(const std::string& fn,
+              std::vector<std::string>& files,
+              bool recursive = false);
+
+void dirList(const std::string& fn,
+             std::vector<std::string>& dirs,
+             bool recursive = false);
+
+// read list of files from commandline
+// if "recursive" expand directories to list of files
+void fileList(int argc, char** argv, int optind,
+              std::vector<std::string>& files,
+              bool recursive = false);
+
+void strings2File(const std::vector<std::string>& s, const std::string& fn);
+void file2Strings(const std::string& fn, std::vector<std::string>& s);
+
+bool dirExists(const std::string& name);
+bool fileExists(const std::string& name);
+
+Strings myPopen(const std::string& cmd,
+                int& rc, bool debug,
+                const std::string& logfn = "");
+
+void removeLock(const std::string& lockfilename);
+void createLock(const std::string& lockfilename);
+
+#endif

+ 770 - 0
kind.ag

@@ -0,0 +1,770 @@
+#include <dirent.h>
+#include <sys/stat.h>
+#include <cstring>
+#include <unistd.h>
+
+#include <iostream>
+#include <fstream>
+
+#include <string>
+#include <vector>
+#include <set>
+
+#include <algorithm>
+
+#include "stringtools.h"
+#include "Exception.h"
+
+#include "DateTime.h"
+#include "FileName.h"
+#include "KindConfig.h"
+#include "filetools.h"
+#include "Lexer.h"
+#include "rulecomp.h"
+#include "Strings.h"
+
+/*AppGen
+  %%  Beschreibung des Programmes:
+  prog: archiving backup
+  %% Beschreibung Parameter
+  % symbolischerName, Art, Typ,   Variablenname, Erklärung, Default-Wert
+  para: vault_or_group, required, string, vault, Vault to backup
+  %% Beschreibung der Optionen
+  % kurz-Option, lang-Option, Typ, Variablenname, Erklärung, Default-Wert
+
+  opt: f, full, void, fullImage, Force full image == initial backup, false
+  opt: c, masterconfig, string, masterConfig, Master config file, ""
+  opt2: if not given or empty kind looks for
+  opt2:   /etc/kind/master.conf
+  opt2:   /ffp/etc/kind/master.conf
+  opt: B, backuponly, void, backupOnly, Only backup/no expire, false
+  opt: E, expireonly, void, expireOnly, Only expire/no backup, false
+  opt: D, dryrun, Void, dryRun, Dry run (no real backup), false
+  opt: v, verbose, Void, verbose,  Verbose,  false
+  opt: d, debug, Void, debug, Debug output of many data, false
+  opt: q, quiet, Void, quiet, Be quiet - no messages, false
+  opt: h, help, usage, ignored , This help
+AppGen*/
+
+using namespace std;
+
+/*AppGen:Global*/
+
+vector<string> banks;
+
+typedef pair<long int, long int> Sizes;
+map<string, Sizes> sizes;
+
+void verbosePrint(const string& text)
+{
+  if (verbose)
+    cout << "  " << text << endl;
+}
+
+void debugPrint(const string& text)
+{
+  if (verbose)
+    cout << "    " << text << endl;
+}
+
+void readMasterConfig(const string& fn, KindConfig& conf)
+{
+  verbosePrint("reading global config " + fn);
+  conf.addFile(fn);
+  banks = conf.getStrings("bank");
+  if (banks.empty())
+    throw Exception("read main config", "no banks defined");
+}
+
+string findVault(const string& v)
+{
+  bool found = false;
+  FileName fn;
+  fn.setName(v);
+  for (unsigned int i = 0; !found && i < banks.size(); ++i)
+    {
+      fn.setPath(banks[i]);
+      if (dirExists(fn.getFileName()))
+        found = true;
+    }
+  if (!found)
+    throw Exception("find vault", v + " not found");
+  verbosePrint("using vault " + fn.getFileName());
+  return fn.getFileName();
+}
+
+void readVaultConfig(const string& vaultConfigName, KindConfig& conf)
+{
+  FileName fn(vaultConfigName);
+
+  verbosePrint("reading vault config " + fn.getFileName());
+
+  conf.addFile(fn.getFileName());
+}
+
+string getImageName(const KindConfig& conf)
+{
+  bool nonPortable = false;
+  string res = conf.getString("imageName");
+  for (unsigned int i = 0; !nonPortable && i < res.size(); ++i)
+    {
+      char c = res[i];
+      if (!isalnum(c) && c != '.' && c != '_')
+        nonPortable = true;
+    }
+  if (nonPortable)
+    throw Exception("getImageName", "Invalid character in image name " + res);
+  return res;
+}
+
+bool isValidImage(const string& imageName)
+{
+  return dirExists(imageName) &&
+         !fileExists(imageName + "/error") &&
+         dirExists(imageName + "/tree");
+}
+
+Strings findValidImages(const string& vaultpath, const KindConfig& conf)
+{
+  Strings imageList;
+  debugPrint("searching images in " + vaultpath);
+  dirList(vaultpath, imageList);
+
+  Strings validImageList;
+  for (unsigned int i = 0; i < imageList.size(); ++i)
+    {
+      FileName fn(imageList[i]);
+      string imgname = getImageName(conf);
+      int len = imgname.length();
+      if (fn.getName().substr(0, len) == imgname)
+        {
+          debugPrint("Checking " + imageList[i]);
+          if (isValidImage(imageList[i]))
+            validImageList.push_back(imageList[i]);
+        }
+    }
+  if (validImageList.empty())
+    throw Exception("Find reference", "No reference found");
+  sort(validImageList.begin(), validImageList.end());
+  return validImageList;
+}
+
+void backupVault(const string& vault,
+                 KindConfig conf /*Copy!*/ ,
+                 const DateTime& imageTime,
+                 bool fullImage)
+{
+  if (!quiet)
+    cout << DateTime::now().getString('h') << ": Backup of vault " << vault << endl;
+  try
+    {
+      sizes[vault].second = 0; // nothing backed up yet
+      string vaultpath = findVault(vault);
+      const string& vaultConfigName = vaultpath + '/' + conf.getString("vaultConfigName");
+      readVaultConfig(vaultConfigName, conf);
+      if (debug)
+        {
+          cout << "vault config:" << endl;
+          conf.print();
+        }
+
+      string imageName = getImageName(conf);
+      if (!imageName.empty())
+        imageName += '-';
+      string imageFullName =  vaultpath + "/" + imageName ;
+
+      if (conf.getBool("longImageName"))
+        imageFullName += imageTime.getString('m');
+      else
+        imageFullName += imageTime.getString('s');
+
+      verbosePrint("backup to \"" + imageFullName + "\"");
+
+      // find reference image
+      string referenceImage;
+      if (!fullImage)
+        {
+          Strings validImageList = findValidImages(vaultpath, conf);
+          // last image is newest image
+          referenceImage = validImageList.back();
+        }
+
+      // create image path
+      if (!dryRun)
+        if (mkdir(imageFullName.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
+          throw Exception("Create image", "failed to create " + imageFullName);
+
+      // error message
+      // we write an generic error message to mark backup as unsuccessful
+      // will be deleted at successful end of rsync
+      string errorfile = imageFullName + "/error";
+      if (!dryRun)
+        {
+          ofstream error(errorfile);
+          error << "failed" << endl;
+          error.close();
+        }
+
+      string host;
+      if (conf.hasKey("host"))
+        host = conf.getString("host");
+
+      string server;
+      if (conf.hasKey("server"))
+        server = conf.getString("server");
+
+      if (!host.empty() && !server.empty())
+        throw Exception("backupVault", "Cannot have host and server");
+
+      string path = conf.getString("path");
+      if (path.empty())
+        throw Exception("rsync", "empty source path");
+      if (path.back() != '/')
+        path += '/';
+
+      string rsyncCmd = "rsync -vrltH --delete --stats -D --numeric-ids ";
+      if (!conf.getBool("ignorePermission"))
+        rsyncCmd += "-pgo";
+      vector<string> rso = conf.getStrings("rsyncOption");
+      for (string opt : rso)
+        rsyncCmd += opt + " ";
+
+      if (!host.empty())  // shell mode
+        {
+          // cout << "USING SHELLMODE '" << host << "'" << endl;
+          string remoteShell = conf.getString("remoteShell");
+          string userAtHost = conf.getString("user") + "@" +
+                              conf.getString("host");
+          string rshCommand = remoteShell;
+          if (remoteShell.empty())
+            rshCommand = "ssh";
+
+          rshCommand += " " + userAtHost;
+
+          // excludes
+          Strings excluded;
+
+          string userExcludeCommand = conf.getString("userExcludeCommand");
+
+          if (!userExcludeCommand.empty())
+            {
+              replacePlaceHolder(userExcludeCommand, "%path", path);
+              string excludeCommand = rshCommand + " " + userExcludeCommand;
+
+              verbosePrint("searching for exclusions (" + excludeCommand + ")");
+
+              int rc;
+              excluded = myPopen(excludeCommand, rc, debug);
+              if (rc > 0)
+                throw Exception("Find exludes", "Search for excludes failed");
+
+              for (unsigned int i = 0; i < excluded.size(); ++i)
+                {
+                  FileName fn(excluded[i]);
+                  excluded[i] = '/' + fn.getPath();
+                  debugPrint("Excluding: " + excluded[i]);
+                }
+            }
+
+          string userExcludeFile = conf.getString("userExcludeFile");
+          if (!userExcludeFile.empty())
+            {
+              userExcludeFile = path + userExcludeFile;
+              string getExcludeFileCommand = rshCommand;
+              getExcludeFileCommand += " \" if [ -f '" + userExcludeFile + "' ]; then ";
+              getExcludeFileCommand += " cat '" + userExcludeFile + "' ; fi \"";
+              // cout << getExcludeFileCommand << endl;
+              int rc;
+              Strings excludes2 = myPopen(getExcludeFileCommand, rc, debug);
+              if (rc == 0)
+                excluded += excludes2;
+            }
+
+          if (conf.hasKey("exclude"))
+            excluded += conf.getStrings("exclude");
+
+          if (!dryRun)
+            strings2File(excluded, imageFullName + "/exclude");
+
+          // rsync image
+
+          if (!remoteShell.empty())
+            rsyncCmd += " -e \'" + remoteShell + "\' ";
+
+          rsyncCmd += "--exclude-from=" + imageFullName + "/exclude ";
+          if (!referenceImage.empty())
+            rsyncCmd += "--link-dest=" + referenceImage + "/tree ";
+          rsyncCmd += userAtHost + ":" + path + " ";
+          rsyncCmd += imageFullName + "/tree";
+        } // shell mode
+      else
+        {
+          // cout << "USING SERVERMODE" << endl;
+          vector<string> excluded;
+          if (conf.hasKey("exclude"))
+            {
+              Strings excludes = conf.getStrings("exclude");
+              for (string s : excludes)
+                excluded.push_back(s);
+            }
+
+          if (!dryRun)
+            strings2File(excluded, imageFullName + "/exclude");
+
+          rsyncCmd += "--exclude-from=" + imageFullName + "/exclude ";
+          if (!referenceImage.empty())
+            rsyncCmd += "--link-dest=" + referenceImage + "/tree ";
+          rsyncCmd += conf.getString("server") + "::" + path + " ";
+          rsyncCmd += imageFullName + "/tree";
+        }
+
+      debugPrint("Action: " + rsyncCmd);
+
+      vector<string> backupResult;
+      if (!dryRun)
+        {
+          verbosePrint("syncing (" + rsyncCmd + ")");
+          int rc;
+          backupResult = myPopen(rsyncCmd, rc, debug, imageFullName + "/rsync-log");
+          // strings2File(backupResult, imageFullName + "/rsync-log");
+          if (rc == 0 || rc == 24) // "no error" or "vanished source files" (ignored)
+            {
+              unlink(errorfile.c_str());
+              string lastLink = vaultpath + "/last";
+              unlink(lastLink.c_str());
+              symlink(imageFullName.c_str(), lastLink.c_str());
+              long int st = 0;
+              long int sc = 0;
+              for (auto bl : backupResult)
+                {
+                  if (bl.substr(0, 15) == "Total file size")
+                    st = getNumber(bl);
+                  else if (bl.substr(0, 27) == "Total transferred file size")
+                    sc = getNumber(bl);
+                }
+              // sizes[vault] = pair<long int, long int>(st, sc);
+              sizes[vault] = Sizes(st, sc);
+              //  cout << vault << " " << st << " || " << sc << endl;
+            }
+          else
+            throw Exception("Backup", "Failed to execute rsync");
+        }
+      else
+        cout << "Not executing " << rsyncCmd << endl;
+    }
+  catch (Exception ex)
+    {
+      cerr << "Exception in vault " << vault << ": " << ex.what() << endl;
+    }
+}
+
+DateTime imageDate(const string& image)
+{
+  FileName fn(image);
+  Strings ss;
+  split(fn.getName(), ss, '-');
+  if (ss.size() < 5)
+    throw Exception("imageDate", "image date not available");
+  int Y = stoi(ss[1]);
+  int M = stoi(ss[2]);
+  int D = stoi(ss[3]);
+  int h = stoi(ss[4]);
+  int m = 0, s = 0;
+  if (ss.size() > 5) // longImageName
+    m = stoi(ss[5]);
+  if (ss.size() > 6)
+    s = stoi(ss[6]);
+  return DateTime(Y, M, D, h, m, s);
+}
+
+void parseRule(string rule,
+               set<int>& M, set<int>& D, set<int>& W, set<int>& h,
+               time_t& exptime)
+{
+  for (unsigned int i = 0; i < rule.size(); ++i)
+    rule[i] = tolower(rule[i]);
+
+  substitute(rule, ' ', ',');
+  reduceToOne(rule, ',');
+
+  // rule = hour wday mday month <exptime>
+  Lexer p(rule);
+
+  h = getValues(p, 0, 23); // hour
+
+  p.expect(',');
+
+  W = getValues(p, 0, 7, 1); // wday
+
+  p.expect(',');
+
+  D = getValues(p, 1, 31); // day of month
+
+  p.expect(',');
+
+  M = getValues(p, 1, 12, 2); // month
+
+#if 0
+  // debug-output
+  cout << "hour: ";
+  for (int i : h)
+    cout << i << " ";
+  cout << endl;
+  cout << "wday: ";
+  for (int i : W)
+    cout << i << " ";
+  cout << endl;
+  cout << "mday: ";
+  for (int i : D)
+    cout << i << " ";
+  cout << endl;
+  cout << "month: ";
+  for (int i : M)
+    cout << i << " ";
+  cout << endl;
+#endif
+
+  string ts = p.getAll();
+  substitute(ts, ',', ' ');
+  exptime = stot(ts);
+}
+
+int removeDir(const string& path)
+{
+  debugPrint("removeDir " + path);
+
+  DIR* d = opendir(path.c_str());
+
+  int r = -1;
+  if (d)
+    {
+      struct dirent* p;
+
+      r = 0;
+
+      while (!r && (p = readdir(d)))
+        {
+          int r2 = 0;
+
+          string fn = p->d_name;
+
+          if (fn != "." && fn != "..")
+            {
+              fn = path + "/" + fn;
+
+              debugPrint("-- " + fn);
+
+              struct stat statbuf;
+              if (lstat(fn.c_str(), &statbuf) == 0)
+                {
+                  if (S_ISLNK(statbuf.st_mode))
+                    {
+                      debugPrint("Remove link " + fn);
+                      r2 = unlink(fn.c_str());
+                    }
+                  else if (S_ISDIR(statbuf.st_mode))
+                    {
+                      debugPrint("Remove dir " + fn);
+                      r2 = removeDir(fn);
+                    }
+                  else
+                    {
+                      debugPrint("Remove file " + fn);
+                      r2 = unlink(fn.c_str());
+                    }
+                }
+              else
+                {
+                  cout << "stat(" << fn << ") failed" << endl;
+                  // we assume "file" here
+                  r2 = unlink(fn.c_str());
+                }
+            }
+          r = r2;
+        }
+
+      closedir(d);
+    }
+
+  if (r == 0)
+    {
+      debugPrint("Remove Dir itself " + path);
+
+      r = rmdir(path.c_str());
+    }
+
+  return r;
+}
+
+#if 0
+int removeDir(const string& dname)
+{
+  int rc = 0;
+  if (!dryRun)
+    {
+      Strings files;
+      // subdirectories
+      dirList(dname, files);
+      for (unsigned int i = 0; i < files.size(); ++i)
+        {
+          debugPrint("Remove dir " + files[i]);
+          for (unsigned int i = 0; i < files.size(); ++i)
+            rc += removeDir(files[i]);
+        }
+      files.clear();
+
+      // files in directory
+      fileList(dname, files);
+      for (unsigned int i = 0; i < files.size(); ++i)
+        {
+          debugPrint("unlink " + files[i]);
+          if (!dryRun)
+            {
+              if (unlink(files[i].c_str()) != 0)
+                rc++;
+            }
+        }
+      debugPrint("rmdir " + dname);
+
+      // directory
+      if (rmdir(dname.c_str()) != 0)
+        rc++;
+    }
+
+  return rc;
+}
+#endif
+
+void expireVault(const string& vault, KindConfig conf, DateTime now)
+{
+  if (!quiet)
+    cout << DateTime::now().getString('h') << ": Expiring images in vault " << vault << endl;
+
+  string vaultpath = findVault(vault);
+  debugPrint("searching images in " + vaultpath);
+
+  const string& vaultConfigName = vaultpath + '/' + conf.getString("vaultConfigName");
+  readVaultConfig(vaultConfigName, conf);
+  if (debug)
+    {
+      cout << "vault config:" << endl;
+      conf.print();
+    }
+
+  Strings dirlist; // list of subdirectories
+  dirList(vaultpath, dirlist);
+
+  Strings validImages;
+  Strings invalidImages;
+  string imgname = getImageName(conf);
+
+  for (unsigned int i = 0; i < dirlist.size(); ++i)
+    {
+      FileName fn(dirlist[i]);
+      if (startsWith(fn.getName(), imgname)) // dir is image ?
+        {
+          debugPrint(dirlist[i]);
+
+          DateTime t = imageDate(dirlist[i]);
+
+          if (t != now) // ignore just created image
+            {
+              if (!isValidImage(dirlist[i])) // invalid image?
+                {
+                  invalidImages.push_back(dirlist[i]);
+                  debugPrint("- invalid image");
+                }
+              else
+                {
+                  validImages.push_back(dirlist[i]);
+                  debugPrint("- valid image");
+                }
+            }
+          else
+            debugPrint("- current image - ignored");
+        }
+    }
+
+  for (unsigned int i = 0; i < invalidImages.size(); ++i)
+    {
+      try
+        {
+          DateTime t = imageDate(invalidImages[i]);
+          DateTime expireTime = t + stot(conf.getString("expireFailedImage"));
+          if (debug)
+            {
+              cout << "image: " << t.getString('h') << "  expire: " << expireTime.getString('h') << endl;
+              cout << " now: " << now.getString('h') << endl;
+            }
+          if (expireTime < now)
+            {
+              if (!quiet)
+                cout << "  removing invalid image " << invalidImages[i] << endl;
+              if (removeDir(invalidImages[i]) != 0)
+                cout << "Error removing " <<  invalidImages[i] << endl;
+            }
+        }
+      catch (Exception ex)
+        {
+          cerr << "Exception: " << ex.what() << endl;
+        }
+    }
+
+  sort(validImages.begin(), validImages.end()); // lexicographical order == temporal order
+  for (unsigned int i = 0;
+       i < validImages.size() - 1; // never expire latest image
+       ++i)
+    {
+      try
+        {
+          DateTime imageTime = imageDate(validImages[i]);
+          DateTime expireTime = DateTime::now() + 100; // don't expire if  no rule found
+          Strings expireRules = conf.getStrings("expireRule");
+          int ruleNr = 0;
+          for (unsigned int k = 0; k < expireRules.size(); ++k)
+            {
+              debugPrint("Checking rule " + expireRules[k]);
+
+              set<int> M, D, W, h;
+              set<int> Y, m, s;
+              time_t expirePeriod;
+              parseRule(expireRules[k], M, D, W, h, expirePeriod);
+              //    cout << M << " " << D << " " << W << " " << h << " " << expirePeriod << endl;
+
+              if (imageTime.match(Y, M, D, W, h, m, s))
+                {
+                  debugPrint("match");
+                  expireTime = imageTime + expirePeriod;
+                  ruleNr = k;
+                }
+            }
+          if (debug)
+            {
+              cout << "image: " << imageTime.getString('h') << "  expire: " << expireTime.getString('h') << endl;
+              cout << " now: " << now.getString('h') << endl;
+            }
+          if (now > expireTime)
+            {
+              if (!quiet)
+                cout << "removing " << validImages[i] << " rule=" << expireRules[ruleNr] << endl;
+              removeDir(validImages[i]);
+            }
+        }
+      catch (Exception ex)
+        {
+          cerr << "Exception: " << ex.what() << endl;
+        }
+    }
+}
+
+/*AppGen:Main*/
+int main(int argc, char* argv[])
+{
+  /*AppGen:MainEnd*/
+
+  int exitCode = 0;
+  string lockFile;
+  try
+    {
+      if (debug)
+        verbose = true;
+
+      KindConfig conf;
+
+      // default-values
+      conf.add("imageName", "image");
+      conf.add("vaultConfigName", "kind/vault.conf");
+      conf.add("expireFailedImage", "3 days");
+      conf.add("expireRule", "* * * * 1 month");
+      conf.add("rsyncOption", ""); // no additional rsync option
+      conf.add("remoteShell", "");
+      conf.add("lockfile", "/var/lock/kind");
+      conf.add("userExcludeFile", "nobackup.list");
+      conf.add("userExcludeCommand",
+               "find %path -type f -iname '*nobackup' -printf '%P\\\\n'");
+
+      conf.add("logSize", "");
+
+      if (!masterConfig.empty())
+        readMasterConfig(masterConfig, conf);
+      else if (fileExists("/etc/kind/master.conf"))
+        readMasterConfig("etc/kind/master.conf", conf);
+      else if (fileExists("/ffp/etc/kind/master.conf"))
+        readMasterConfig("/ffp/etc/kind/master.conf", conf);
+      else
+        throw Exception("MasterConfig", "no file");
+
+      if (debug)
+        {
+          cout << "global config:" << endl;
+          conf.print();
+        }
+
+      lockFile = conf.getString("lockfile");
+      createLock(lockFile);
+
+      DateTime imageTime = DateTime::now();
+      string logSizeFile = conf.getString("logSize");
+      if (!logSizeFile.empty() && fileExists(logSizeFile))
+        {
+          vector<string> ss;
+          file2Strings(logSizeFile, ss);
+          for (auto s : ss)
+            {
+              unsigned int i = 0;
+              string v = getWord(s, i);
+              long int s1 = getLongInt(s, i);
+              long int s2 = getLongInt(s, i);
+              sizes[v] = Sizes(s1, s2);
+            }
+        }
+
+      vector<string> vaults;
+      string groupname = "group_" + vault;
+      if (conf.hasKey(groupname))
+        vaults = conf.getStrings(groupname);
+      else
+        vaults.push_back(vault);
+
+      if (!expireOnly)
+        for (unsigned int i = 0; i < vaults.size(); ++i)
+          {
+            backupVault(vaults[i], conf, imageTime, fullImage);
+            if (!logSizeFile.empty())
+              {
+                Strings st;
+                for (auto s : sizes)
+                  {
+                    string h = s.first + " " + to_string(s.second.first) + " " + to_string(s.second.second);
+                    st.push_back(h);
+                  }
+                strings2File(st, logSizeFile);
+              }
+          }
+
+      if (!backupOnly)
+        for (unsigned int i = 0; i < vaults.size(); ++i)
+          expireVault(vaults[i], conf, imageTime);
+
+      if (!quiet)
+        cout << DateTime::now().getString('h') << ": finished" << endl;
+
+    }
+  catch (const Exception& ex)
+    {
+      cerr << "Exception: " << ex.what() << endl;
+      exitCode = 1;
+    }
+  catch (const char* msg)
+    {
+      cerr << "Exception(char*): " << msg << endl;
+      exitCode = 1;
+    }
+  catch (const string& msg)
+    {
+      cerr << "Exception(string): " << msg << endl;
+      exitCode = 1;
+    }
+  removeLock(lockFile);
+  return exitCode;
+}

+ 1673 - 0
kind.cpp

@@ -0,0 +1,1673 @@
+# 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 "FileName.h"
+# 20 "kind.ag"
+#include "KindConfig.h"
+# 21 "kind.ag"
+#include "filetools.h"
+# 22 "kind.ag"
+#include "Lexer.h"
+# 23 "kind.ag"
+#include "rulecomp.h"
+# 24 "kind.ag"
+#include "Strings.h"
+# 25 "kind.ag"
+
+# 26 "kind.ag"
+/*AppGen
+# 27 "kind.ag"
+  %%  Beschreibung des Programmes:
+# 28 "kind.ag"
+  prog: archiving backup
+# 29 "kind.ag"
+  %% Beschreibung Parameter
+# 30 "kind.ag"
+  % symbolischerName, Art, Typ,   Variablenname, Erklärung, Default-Wert
+# 31 "kind.ag"
+  para: vault_or_group, required, string, vault, Vault to backup
+# 32 "kind.ag"
+  %% Beschreibung der Optionen
+# 33 "kind.ag"
+  % kurz-Option, lang-Option, Typ, Variablenname, Erklärung, Default-Wert
+# 34 "kind.ag"
+
+# 35 "kind.ag"
+  opt: f, full, void, fullImage, Force full image == initial backup, false
+# 36 "kind.ag"
+  opt: c, masterconfig, string, masterConfig, Master config file, ""
+# 37 "kind.ag"
+  opt2: if not given or empty kind looks for
+# 38 "kind.ag"
+  opt2:   /etc/kind/master.conf
+# 39 "kind.ag"
+  opt2:   /ffp/etc/kind/master.conf
+# 40 "kind.ag"
+  opt: B, backuponly, void, backupOnly, Only backup/no expire, false
+# 41 "kind.ag"
+  opt: E, expireonly, void, expireOnly, Only expire/no backup, false
+# 42 "kind.ag"
+  opt: D, dryrun, Void, dryRun, Dry run (no real backup), false
+# 43 "kind.ag"
+  opt: v, verbose, Void, verbose,  Verbose,  false
+# 44 "kind.ag"
+  opt: d, debug, Void, debug, Debug output of many data, false
+# 45 "kind.ag"
+  opt: q, quiet, Void, quiet, Be quiet - no messages, false
+# 46 "kind.ag"
+  opt: h, help, usage, ignored , This help
+# 47 "kind.ag"
+AppGen*/
+# 48 "kind.ag"
+
+# 49 "kind.ag"
+using namespace std;
+# 50 "kind.ag"
+
+# 51 "kind.ag"
+/*AppGen:Global*/
+#include <getopt.h>
+#include <string>
+#include <string>
+bool dryRun = false;
+bool verbose = false;
+bool debug = false;
+bool quiet = false;
+/*AppGen:GlobalEnd*/
+# 52 "kind.ag"
+
+# 53 "kind.ag"
+vector<string> banks;
+# 54 "kind.ag"
+
+# 55 "kind.ag"
+typedef pair<long int, long int> Sizes;
+# 56 "kind.ag"
+map<string, Sizes> sizes;
+# 57 "kind.ag"
+
+# 58 "kind.ag"
+void verbosePrint(const string& text)
+# 59 "kind.ag"
+{
+# 60 "kind.ag"
+  if (verbose)
+# 61 "kind.ag"
+    cout << "  " << text << endl;
+# 62 "kind.ag"
+}
+# 63 "kind.ag"
+
+# 64 "kind.ag"
+void debugPrint(const string& text)
+# 65 "kind.ag"
+{
+# 66 "kind.ag"
+  if (verbose)
+# 67 "kind.ag"
+    cout << "    " << text << endl;
+# 68 "kind.ag"
+}
+# 69 "kind.ag"
+
+# 70 "kind.ag"
+void readMasterConfig(const string& fn, KindConfig& conf)
+# 71 "kind.ag"
+{
+# 72 "kind.ag"
+  verbosePrint("reading global config " + fn);
+# 73 "kind.ag"
+  conf.addFile(fn);
+# 74 "kind.ag"
+  banks = conf.getStrings("bank");
+# 75 "kind.ag"
+  if (banks.empty())
+# 76 "kind.ag"
+    throw Exception("read main config", "no banks defined");
+# 77 "kind.ag"
+}
+# 78 "kind.ag"
+
+# 79 "kind.ag"
+string findVault(const string& v)
+# 80 "kind.ag"
+{
+# 81 "kind.ag"
+  bool found = false;
+# 82 "kind.ag"
+  FileName fn;
+# 83 "kind.ag"
+  fn.setName(v);
+# 84 "kind.ag"
+  for (unsigned int i = 0; !found && i < banks.size(); ++i)
+# 85 "kind.ag"
+    {
+# 86 "kind.ag"
+      fn.setPath(banks[i]);
+# 87 "kind.ag"
+      if (dirExists(fn.getFileName()))
+# 88 "kind.ag"
+        found = true;
+# 89 "kind.ag"
+    }
+# 90 "kind.ag"
+  if (!found)
+# 91 "kind.ag"
+    throw Exception("find vault", v + " not found");
+# 92 "kind.ag"
+  verbosePrint("using vault " + fn.getFileName());
+# 93 "kind.ag"
+  return fn.getFileName();
+# 94 "kind.ag"
+}
+# 95 "kind.ag"
+
+# 96 "kind.ag"
+void readVaultConfig(const string& vaultConfigName, KindConfig& conf)
+# 97 "kind.ag"
+{
+# 98 "kind.ag"
+  FileName fn(vaultConfigName);
+# 99 "kind.ag"
+
+# 100 "kind.ag"
+  verbosePrint("reading vault config " + fn.getFileName());
+# 101 "kind.ag"
+
+# 102 "kind.ag"
+  conf.addFile(fn.getFileName());
+# 103 "kind.ag"
+}
+# 104 "kind.ag"
+
+# 105 "kind.ag"
+string getImageName(const KindConfig& conf)
+# 106 "kind.ag"
+{
+# 107 "kind.ag"
+  bool nonPortable = false;
+# 108 "kind.ag"
+  string res = conf.getString("imageName");
+# 109 "kind.ag"
+  for (unsigned int i = 0; !nonPortable && i < res.size(); ++i)
+# 110 "kind.ag"
+    {
+# 111 "kind.ag"
+      char c = res[i];
+# 112 "kind.ag"
+      if (!isalnum(c) && c != '.' && c != '_')
+# 113 "kind.ag"
+        nonPortable = true;
+# 114 "kind.ag"
+    }
+# 115 "kind.ag"
+  if (nonPortable)
+# 116 "kind.ag"
+    throw Exception("getImageName", "Invalid character in image name " + res);
+# 117 "kind.ag"
+  return res;
+# 118 "kind.ag"
+}
+# 119 "kind.ag"
+
+# 120 "kind.ag"
+bool isValidImage(const string& imageName)
+# 121 "kind.ag"
+{
+# 122 "kind.ag"
+  return dirExists(imageName) &&
+# 123 "kind.ag"
+  !fileExists(imageName + "/error") &&
+# 124 "kind.ag"
+  dirExists(imageName + "/tree");
+# 125 "kind.ag"
+}
+# 126 "kind.ag"
+
+# 127 "kind.ag"
+Strings findValidImages(const string& vaultpath, const KindConfig& conf)
+# 128 "kind.ag"
+{
+# 129 "kind.ag"
+  Strings imageList;
+# 130 "kind.ag"
+  debugPrint("searching images in " + vaultpath);
+# 131 "kind.ag"
+  dirList(vaultpath, imageList);
+# 132 "kind.ag"
+
+# 133 "kind.ag"
+  Strings validImageList;
+# 134 "kind.ag"
+  for (unsigned int i = 0; i < imageList.size(); ++i)
+# 135 "kind.ag"
+    {
+# 136 "kind.ag"
+      FileName fn(imageList[i]);
+# 137 "kind.ag"
+      string imgname = getImageName(conf);
+# 138 "kind.ag"
+      int len = imgname.length();
+# 139 "kind.ag"
+      if (fn.getName().substr(0, len) == imgname)
+# 140 "kind.ag"
+        {
+# 141 "kind.ag"
+          debugPrint("Checking " + imageList[i]);
+# 142 "kind.ag"
+          if (isValidImage(imageList[i]))
+# 143 "kind.ag"
+            validImageList.push_back(imageList[i]);
+# 144 "kind.ag"
+        }
+# 145 "kind.ag"
+    }
+# 146 "kind.ag"
+  if (validImageList.empty())
+# 147 "kind.ag"
+    throw Exception("Find reference", "No reference found");
+# 148 "kind.ag"
+  sort(validImageList.begin(), validImageList.end());
+# 149 "kind.ag"
+  return validImageList;
+# 150 "kind.ag"
+}
+# 151 "kind.ag"
+
+# 152 "kind.ag"
+void backupVault(const string& vault,
+# 153 "kind.ag"
+                 KindConfig conf /*Copy!*/ ,
+# 154 "kind.ag"
+                 const DateTime& imageTime,
+# 155 "kind.ag"
+                 bool fullImage)
+# 156 "kind.ag"
+{
+# 157 "kind.ag"
+  if (!quiet)
+# 158 "kind.ag"
+    cout << DateTime::now().getString('h') << ": Backup of vault " << vault << endl;
+# 159 "kind.ag"
+  try
+# 160 "kind.ag"
+    {
+# 161 "kind.ag"
+      sizes[vault].second = 0; // nothing backed up yet
+# 162 "kind.ag"
+      string vaultpath = findVault(vault);
+# 163 "kind.ag"
+      const string& vaultConfigName = vaultpath + '/' + conf.getString("vaultConfigName");
+# 164 "kind.ag"
+      readVaultConfig(vaultConfigName, conf);
+# 165 "kind.ag"
+      if (debug)
+# 166 "kind.ag"
+        {
+# 167 "kind.ag"
+          cout << "vault config:" << endl;
+# 168 "kind.ag"
+          conf.print();
+# 169 "kind.ag"
+        }
+# 170 "kind.ag"
+
+# 171 "kind.ag"
+      string imageName = getImageName(conf);
+# 172 "kind.ag"
+      if (!imageName.empty())
+# 173 "kind.ag"
+        imageName += '-';
+# 174 "kind.ag"
+      string imageFullName =  vaultpath + "/" + imageName ;
+# 175 "kind.ag"
+
+# 176 "kind.ag"
+      if (conf.getBool("longImageName"))
+# 177 "kind.ag"
+        imageFullName += imageTime.getString('m');
+# 178 "kind.ag"
+      else
+# 179 "kind.ag"
+        imageFullName += imageTime.getString('s');
+# 180 "kind.ag"
+
+# 181 "kind.ag"
+      verbosePrint("backup to \"" + imageFullName + "\"");
+# 182 "kind.ag"
+
+# 183 "kind.ag"
+      // find reference image
+# 184 "kind.ag"
+      string referenceImage;
+# 185 "kind.ag"
+      if (!fullImage)
+# 186 "kind.ag"
+        {
+# 187 "kind.ag"
+          Strings validImageList = findValidImages(vaultpath, conf);
+# 188 "kind.ag"
+          // last image is newest image
+# 189 "kind.ag"
+          referenceImage = validImageList.back();
+# 190 "kind.ag"
+        }
+# 191 "kind.ag"
+
+# 192 "kind.ag"
+      // create image path
+# 193 "kind.ag"
+      if (!dryRun)
+# 194 "kind.ag"
+        if (mkdir(imageFullName.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
+# 195 "kind.ag"
+          throw Exception("Create image", "failed to create " + imageFullName);
+# 196 "kind.ag"
+
+# 197 "kind.ag"
+      // error message
+# 198 "kind.ag"
+      // we write an generic error message to mark backup as unsuccessful
+# 199 "kind.ag"
+      // will be deleted at successful end of rsync
+# 200 "kind.ag"
+      string errorfile = imageFullName + "/error";
+# 201 "kind.ag"
+      if (!dryRun)
+# 202 "kind.ag"
+        {
+# 203 "kind.ag"
+          ofstream error(errorfile);
+# 204 "kind.ag"
+          error << "failed" << endl;
+# 205 "kind.ag"
+          error.close();
+# 206 "kind.ag"
+        }
+# 207 "kind.ag"
+
+# 208 "kind.ag"
+      string host;
+# 209 "kind.ag"
+      if (conf.hasKey("host"))
+# 210 "kind.ag"
+        host = conf.getString("host");
+# 211 "kind.ag"
+
+# 212 "kind.ag"
+      string server;
+# 213 "kind.ag"
+      if (conf.hasKey("server"))
+# 214 "kind.ag"
+        server = conf.getString("server");
+# 215 "kind.ag"
+
+# 216 "kind.ag"
+      if (!host.empty() && !server.empty())
+# 217 "kind.ag"
+        throw Exception("backupVault", "Cannot have host and server");
+# 218 "kind.ag"
+
+# 219 "kind.ag"
+      string path = conf.getString("path");
+# 220 "kind.ag"
+      if (path.empty())
+# 221 "kind.ag"
+        throw Exception("rsync", "empty source path");
+# 222 "kind.ag"
+      if (path.back() != '/')
+# 223 "kind.ag"
+        path += '/';
+# 224 "kind.ag"
+
+# 225 "kind.ag"
+      string rsyncCmd = "rsync -vrltH --delete --stats -D --numeric-ids ";
+# 226 "kind.ag"
+      if (!conf.getBool("ignorePermission"))
+# 227 "kind.ag"
+        rsyncCmd += "-pgo";
+# 228 "kind.ag"
+      vector<string> rso = conf.getStrings("rsyncOption");
+# 229 "kind.ag"
+      for (string opt : rso)
+# 230 "kind.ag"
+        rsyncCmd += opt + " ";
+# 231 "kind.ag"
+
+# 232 "kind.ag"
+      if (!host.empty())  // shell mode
+# 233 "kind.ag"
+        {
+# 234 "kind.ag"
+          // cout << "USING SHELLMODE '" << host << "'" << endl;
+# 235 "kind.ag"
+          string remoteShell = conf.getString("remoteShell");
+# 236 "kind.ag"
+          string userAtHost = conf.getString("user") + "@" +
+# 237 "kind.ag"
+          conf.getString("host");
+# 238 "kind.ag"
+          string rshCommand = remoteShell;
+# 239 "kind.ag"
+          if (remoteShell.empty())
+# 240 "kind.ag"
+            rshCommand = "ssh";
+# 241 "kind.ag"
+
+# 242 "kind.ag"
+          rshCommand += " " + userAtHost;
+# 243 "kind.ag"
+
+# 244 "kind.ag"
+          // excludes
+# 245 "kind.ag"
+          Strings excluded;
+# 246 "kind.ag"
+
+# 247 "kind.ag"
+          string userExcludeCommand = conf.getString("userExcludeCommand");
+# 248 "kind.ag"
+
+# 249 "kind.ag"
+          if (!userExcludeCommand.empty())
+# 250 "kind.ag"
+            {
+# 251 "kind.ag"
+              replacePlaceHolder(userExcludeCommand, "%path", path);
+# 252 "kind.ag"
+              string excludeCommand = rshCommand + " " + userExcludeCommand;
+# 253 "kind.ag"
+
+# 254 "kind.ag"
+              verbosePrint("searching for exclusions (" + excludeCommand + ")");
+# 255 "kind.ag"
+
+# 256 "kind.ag"
+              int rc;
+# 257 "kind.ag"
+              excluded = myPopen(excludeCommand, rc, debug);
+# 258 "kind.ag"
+              if (rc > 0)
+# 259 "kind.ag"
+                throw Exception("Find exludes", "Search for excludes failed");
+# 260 "kind.ag"
+
+# 261 "kind.ag"
+              for (unsigned int i = 0; i < excluded.size(); ++i)
+# 262 "kind.ag"
+                {
+# 263 "kind.ag"
+                  FileName fn(excluded[i]);
+# 264 "kind.ag"
+                  excluded[i] = '/' + fn.getPath();
+# 265 "kind.ag"
+                  debugPrint("Excluding: " + excluded[i]);
+# 266 "kind.ag"
+                }
+# 267 "kind.ag"
+            }
+# 268 "kind.ag"
+
+# 269 "kind.ag"
+          string userExcludeFile = conf.getString("userExcludeFile");
+# 270 "kind.ag"
+          if (!userExcludeFile.empty())
+# 271 "kind.ag"
+            {
+# 272 "kind.ag"
+              userExcludeFile = path + userExcludeFile;
+# 273 "kind.ag"
+              string getExcludeFileCommand = rshCommand;
+# 274 "kind.ag"
+              getExcludeFileCommand += " \" if [ -f '" + userExcludeFile + "' ]; then ";
+# 275 "kind.ag"
+              getExcludeFileCommand += " cat '" + userExcludeFile + "' ; fi \"";
+# 276 "kind.ag"
+              // cout << getExcludeFileCommand << endl;
+# 277 "kind.ag"
+              int rc;
+# 278 "kind.ag"
+              Strings excludes2 = myPopen(getExcludeFileCommand, rc, debug);
+# 279 "kind.ag"
+              if (rc == 0)
+# 280 "kind.ag"
+                excluded += excludes2;
+# 281 "kind.ag"
+            }
+# 282 "kind.ag"
+
+# 283 "kind.ag"
+          if (conf.hasKey("exclude"))
+# 284 "kind.ag"
+            excluded += conf.getStrings("exclude");
+# 285 "kind.ag"
+
+# 286 "kind.ag"
+          if (!dryRun)
+# 287 "kind.ag"
+            strings2File(excluded, imageFullName + "/exclude");
+# 288 "kind.ag"
+
+# 289 "kind.ag"
+          // rsync image
+# 290 "kind.ag"
+
+# 291 "kind.ag"
+          if (!remoteShell.empty())
+# 292 "kind.ag"
+            rsyncCmd += " -e \'" + remoteShell + "\' ";
+# 293 "kind.ag"
+
+# 294 "kind.ag"
+          rsyncCmd += "--exclude-from=" + imageFullName + "/exclude ";
+# 295 "kind.ag"
+          if (!referenceImage.empty())
+# 296 "kind.ag"
+            rsyncCmd += "--link-dest=" + referenceImage + "/tree ";
+# 297 "kind.ag"
+          rsyncCmd += userAtHost + ":" + path + " ";
+# 298 "kind.ag"
+          rsyncCmd += imageFullName + "/tree";
+# 299 "kind.ag"
+        } // shell mode
+# 300 "kind.ag"
+      else
+# 301 "kind.ag"
+        {
+# 302 "kind.ag"
+          // cout << "USING SERVERMODE" << endl;
+# 303 "kind.ag"
+          vector<string> excluded;
+# 304 "kind.ag"
+          if (conf.hasKey("exclude"))
+# 305 "kind.ag"
+            {
+# 306 "kind.ag"
+              Strings excludes = conf.getStrings("exclude");
+# 307 "kind.ag"
+              for (string s : excludes)
+# 308 "kind.ag"
+                excluded.push_back(s);
+# 309 "kind.ag"
+            }
+# 310 "kind.ag"
+
+# 311 "kind.ag"
+          if (!dryRun)
+# 312 "kind.ag"
+            strings2File(excluded, imageFullName + "/exclude");
+# 313 "kind.ag"
+
+# 314 "kind.ag"
+          rsyncCmd += "--exclude-from=" + imageFullName + "/exclude ";
+# 315 "kind.ag"
+          if (!referenceImage.empty())
+# 316 "kind.ag"
+            rsyncCmd += "--link-dest=" + referenceImage + "/tree ";
+# 317 "kind.ag"
+          rsyncCmd += conf.getString("server") + "::" + path + " ";
+# 318 "kind.ag"
+          rsyncCmd += imageFullName + "/tree";
+# 319 "kind.ag"
+        }
+# 320 "kind.ag"
+
+# 321 "kind.ag"
+      debugPrint("Action: " + rsyncCmd);
+# 322 "kind.ag"
+
+# 323 "kind.ag"
+      vector<string> backupResult;
+# 324 "kind.ag"
+      if (!dryRun)
+# 325 "kind.ag"
+        {
+# 326 "kind.ag"
+          verbosePrint("syncing (" + rsyncCmd + ")");
+# 327 "kind.ag"
+          int rc;
+# 328 "kind.ag"
+          backupResult = myPopen(rsyncCmd, rc, debug, imageFullName + "/rsync-log");
+# 329 "kind.ag"
+          // strings2File(backupResult, imageFullName + "/rsync-log");
+# 330 "kind.ag"
+          if (rc == 0 || rc == 24) // "no error" or "vanished source files" (ignored)
+# 331 "kind.ag"
+            {
+# 332 "kind.ag"
+              unlink(errorfile.c_str());
+# 333 "kind.ag"
+              string lastLink = vaultpath + "/last";
+# 334 "kind.ag"
+              unlink(lastLink.c_str());
+# 335 "kind.ag"
+              symlink(imageFullName.c_str(), lastLink.c_str());
+# 336 "kind.ag"
+              long int st = 0;
+# 337 "kind.ag"
+              long int sc = 0;
+# 338 "kind.ag"
+              for (auto bl : backupResult)
+# 339 "kind.ag"
+                {
+# 340 "kind.ag"
+                  if (bl.substr(0, 15) == "Total file size")
+# 341 "kind.ag"
+                    st = getNumber(bl);
+# 342 "kind.ag"
+                  else if (bl.substr(0, 27) == "Total transferred file size")
+# 343 "kind.ag"
+                    sc = getNumber(bl);
+# 344 "kind.ag"
+                }
+# 345 "kind.ag"
+              // sizes[vault] = pair<long int, long int>(st, sc);
+# 346 "kind.ag"
+              sizes[vault] = Sizes(st, sc);
+# 347 "kind.ag"
+              //  cout << vault << " " << st << " || " << sc << endl;
+# 348 "kind.ag"
+            }
+# 349 "kind.ag"
+          else
+# 350 "kind.ag"
+            throw Exception("Backup", "Failed to execute rsync");
+# 351 "kind.ag"
+        }
+# 352 "kind.ag"
+      else
+# 353 "kind.ag"
+        cout << "Not executing " << rsyncCmd << endl;
+# 354 "kind.ag"
+    }
+# 355 "kind.ag"
+  catch (Exception ex)
+# 356 "kind.ag"
+    {
+# 357 "kind.ag"
+      cerr << "Exception in vault " << vault << ": " << ex.what() << endl;
+# 358 "kind.ag"
+    }
+# 359 "kind.ag"
+}
+# 360 "kind.ag"
+
+# 361 "kind.ag"
+DateTime imageDate(const string& image)
+# 362 "kind.ag"
+{
+# 363 "kind.ag"
+  FileName fn(image);
+# 364 "kind.ag"
+  Strings ss;
+# 365 "kind.ag"
+  split(fn.getName(), ss, '-');
+# 366 "kind.ag"
+  if (ss.size() < 5)
+# 367 "kind.ag"
+    throw Exception("imageDate", "image date not available");
+# 368 "kind.ag"
+  int Y = stoi(ss[1]);
+# 369 "kind.ag"
+  int M = stoi(ss[2]);
+# 370 "kind.ag"
+  int D = stoi(ss[3]);
+# 371 "kind.ag"
+  int h = stoi(ss[4]);
+# 372 "kind.ag"
+  int m = 0, s = 0;
+# 373 "kind.ag"
+  if (ss.size() > 5) // longImageName
+# 374 "kind.ag"
+    m = stoi(ss[5]);
+# 375 "kind.ag"
+  if (ss.size() > 6)
+# 376 "kind.ag"
+    s = stoi(ss[6]);
+# 377 "kind.ag"
+  return DateTime(Y, M, D, h, m, s);
+# 378 "kind.ag"
+}
+# 379 "kind.ag"
+
+# 380 "kind.ag"
+void parseRule(string rule,
+# 381 "kind.ag"
+               set<int>& M, set<int>& D, set<int>& W, set<int>& h,
+# 382 "kind.ag"
+               time_t& exptime)
+# 383 "kind.ag"
+{
+# 384 "kind.ag"
+  for (unsigned int i = 0; i < rule.size(); ++i)
+# 385 "kind.ag"
+    rule[i] = tolower(rule[i]);
+# 386 "kind.ag"
+
+# 387 "kind.ag"
+  substitute(rule, ' ', ',');
+# 388 "kind.ag"
+  reduceToOne(rule, ',');
+# 389 "kind.ag"
+
+# 390 "kind.ag"
+  // rule = hour wday mday month <exptime>
+# 391 "kind.ag"
+  Lexer p(rule);
+# 392 "kind.ag"
+
+# 393 "kind.ag"
+  h = getValues(p, 0, 23); // hour
+# 394 "kind.ag"
+
+# 395 "kind.ag"
+  p.expect(',');
+# 396 "kind.ag"
+
+# 397 "kind.ag"
+  W = getValues(p, 0, 7, 1); // wday
+# 398 "kind.ag"
+
+# 399 "kind.ag"
+  p.expect(',');
+# 400 "kind.ag"
+
+# 401 "kind.ag"
+  D = getValues(p, 1, 31); // day of month
+# 402 "kind.ag"
+
+# 403 "kind.ag"
+  p.expect(',');
+# 404 "kind.ag"
+
+# 405 "kind.ag"
+  M = getValues(p, 1, 12, 2); // month
+# 406 "kind.ag"
+
+# 407 "kind.ag"
+#if 0
+# 408 "kind.ag"
+  // debug-output
+# 409 "kind.ag"
+  cout << "hour: ";
+# 410 "kind.ag"
+  for (int i : h)
+# 411 "kind.ag"
+    cout << i << " ";
+# 412 "kind.ag"
+  cout << endl;
+# 413 "kind.ag"
+  cout << "wday: ";
+# 414 "kind.ag"
+  for (int i : W)
+# 415 "kind.ag"
+    cout << i << " ";
+# 416 "kind.ag"
+  cout << endl;
+# 417 "kind.ag"
+  cout << "mday: ";
+# 418 "kind.ag"
+  for (int i : D)
+# 419 "kind.ag"
+    cout << i << " ";
+# 420 "kind.ag"
+  cout << endl;
+# 421 "kind.ag"
+  cout << "month: ";
+# 422 "kind.ag"
+  for (int i : M)
+# 423 "kind.ag"
+    cout << i << " ";
+# 424 "kind.ag"
+  cout << endl;
+# 425 "kind.ag"
+#endif
+# 426 "kind.ag"
+
+# 427 "kind.ag"
+  string ts = p.getAll();
+# 428 "kind.ag"
+  substitute(ts, ',', ' ');
+# 429 "kind.ag"
+  exptime = stot(ts);
+# 430 "kind.ag"
+}
+# 431 "kind.ag"
+
+# 432 "kind.ag"
+int removeDir(const string& path)
+# 433 "kind.ag"
+{
+# 434 "kind.ag"
+  debugPrint("removeDir " + path);
+# 435 "kind.ag"
+
+# 436 "kind.ag"
+  DIR* d = opendir(path.c_str());
+# 437 "kind.ag"
+
+# 438 "kind.ag"
+  int r = -1;
+# 439 "kind.ag"
+  if (d)
+# 440 "kind.ag"
+    {
+# 441 "kind.ag"
+      struct dirent* p;
+# 442 "kind.ag"
+
+# 443 "kind.ag"
+      r = 0;
+# 444 "kind.ag"
+
+# 445 "kind.ag"
+      while (!r && (p = readdir(d)))
+# 446 "kind.ag"
+        {
+# 447 "kind.ag"
+          int r2 = 0;
+# 448 "kind.ag"
+
+# 449 "kind.ag"
+          string fn = p->d_name;
+# 450 "kind.ag"
+
+# 451 "kind.ag"
+          if (fn != "." && fn != "..")
+# 452 "kind.ag"
+            {
+# 453 "kind.ag"
+              fn = path + "/" + fn;
+# 454 "kind.ag"
+
+# 455 "kind.ag"
+              debugPrint("-- " + fn);
+# 456 "kind.ag"
+
+# 457 "kind.ag"
+              struct stat statbuf;
+# 458 "kind.ag"
+              if (lstat(fn.c_str(), &statbuf) == 0)
+# 459 "kind.ag"
+                {
+# 460 "kind.ag"
+                  if (S_ISLNK(statbuf.st_mode))
+# 461 "kind.ag"
+                    {
+# 462 "kind.ag"
+                      debugPrint("Remove link " + fn);
+# 463 "kind.ag"
+                      r2 = unlink(fn.c_str());
+# 464 "kind.ag"
+                    }
+# 465 "kind.ag"
+                  else if (S_ISDIR(statbuf.st_mode))
+# 466 "kind.ag"
+                    {
+# 467 "kind.ag"
+                      debugPrint("Remove dir " + fn);
+# 468 "kind.ag"
+                      r2 = removeDir(fn);
+# 469 "kind.ag"
+                    }
+# 470 "kind.ag"
+                  else
+# 471 "kind.ag"
+                    {
+# 472 "kind.ag"
+                      debugPrint("Remove file " + fn);
+# 473 "kind.ag"
+                      r2 = unlink(fn.c_str());
+# 474 "kind.ag"
+                    }
+# 475 "kind.ag"
+                }
+# 476 "kind.ag"
+              else
+# 477 "kind.ag"
+                {
+# 478 "kind.ag"
+                  cout << "stat(" << fn << ") failed" << endl;
+# 479 "kind.ag"
+                  // we assume "file" here
+# 480 "kind.ag"
+                  r2 = unlink(fn.c_str());
+# 481 "kind.ag"
+                }
+# 482 "kind.ag"
+            }
+# 483 "kind.ag"
+          r = r2;
+# 484 "kind.ag"
+        }
+# 485 "kind.ag"
+
+# 486 "kind.ag"
+      closedir(d);
+# 487 "kind.ag"
+    }
+# 488 "kind.ag"
+
+# 489 "kind.ag"
+  if (r == 0)
+# 490 "kind.ag"
+    {
+# 491 "kind.ag"
+      debugPrint("Remove Dir itself " + path);
+# 492 "kind.ag"
+
+# 493 "kind.ag"
+      r = rmdir(path.c_str());
+# 494 "kind.ag"
+    }
+# 495 "kind.ag"
+
+# 496 "kind.ag"
+  return r;
+# 497 "kind.ag"
+}
+# 498 "kind.ag"
+
+# 499 "kind.ag"
+#if 0
+# 500 "kind.ag"
+int removeDir(const string& dname)
+# 501 "kind.ag"
+{
+# 502 "kind.ag"
+  int rc = 0;
+# 503 "kind.ag"
+  if (!dryRun)
+# 504 "kind.ag"
+    {
+# 505 "kind.ag"
+      Strings files;
+# 506 "kind.ag"
+      // subdirectories
+# 507 "kind.ag"
+      dirList(dname, files);
+# 508 "kind.ag"
+      for (unsigned int i = 0; i < files.size(); ++i)
+# 509 "kind.ag"
+        {
+# 510 "kind.ag"
+          debugPrint("Remove dir " + files[i]);
+# 511 "kind.ag"
+          for (unsigned int i = 0; i < files.size(); ++i)
+# 512 "kind.ag"
+            rc += removeDir(files[i]);
+# 513 "kind.ag"
+        }
+# 514 "kind.ag"
+      files.clear();
+# 515 "kind.ag"
+
+# 516 "kind.ag"
+      // files in directory
+# 517 "kind.ag"
+      fileList(dname, files);
+# 518 "kind.ag"
+      for (unsigned int i = 0; i < files.size(); ++i)
+# 519 "kind.ag"
+        {
+# 520 "kind.ag"
+          debugPrint("unlink " + files[i]);
+# 521 "kind.ag"
+          if (!dryRun)
+# 522 "kind.ag"
+            {
+# 523 "kind.ag"
+              if (unlink(files[i].c_str()) != 0)
+# 524 "kind.ag"
+                rc++;
+# 525 "kind.ag"
+            }
+# 526 "kind.ag"
+        }
+# 527 "kind.ag"
+      debugPrint("rmdir " + dname);
+# 528 "kind.ag"
+
+# 529 "kind.ag"
+      // directory
+# 530 "kind.ag"
+      if (rmdir(dname.c_str()) != 0)
+# 531 "kind.ag"
+        rc++;
+# 532 "kind.ag"
+    }
+# 533 "kind.ag"
+
+# 534 "kind.ag"
+  return rc;
+# 535 "kind.ag"
+}
+# 536 "kind.ag"
+#endif
+# 537 "kind.ag"
+
+# 538 "kind.ag"
+void expireVault(const string& vault, KindConfig conf, DateTime now)
+# 539 "kind.ag"
+{
+# 540 "kind.ag"
+  if (!quiet)
+# 541 "kind.ag"
+    cout << DateTime::now().getString('h') << ": Expiring images in vault " << vault << endl;
+# 542 "kind.ag"
+
+# 543 "kind.ag"
+  string vaultpath = findVault(vault);
+# 544 "kind.ag"
+  debugPrint("searching images in " + vaultpath);
+# 545 "kind.ag"
+
+# 546 "kind.ag"
+  const string& vaultConfigName = vaultpath + '/' + conf.getString("vaultConfigName");
+# 547 "kind.ag"
+  readVaultConfig(vaultConfigName, conf);
+# 548 "kind.ag"
+  if (debug)
+# 549 "kind.ag"
+    {
+# 550 "kind.ag"
+      cout << "vault config:" << endl;
+# 551 "kind.ag"
+      conf.print();
+# 552 "kind.ag"
+    }
+# 553 "kind.ag"
+
+# 554 "kind.ag"
+  Strings dirlist; // list of subdirectories
+# 555 "kind.ag"
+  dirList(vaultpath, dirlist);
+# 556 "kind.ag"
+
+# 557 "kind.ag"
+  Strings validImages;
+# 558 "kind.ag"
+  Strings invalidImages;
+# 559 "kind.ag"
+  string imgname = getImageName(conf);
+# 560 "kind.ag"
+
+# 561 "kind.ag"
+  for (unsigned int i = 0; i < dirlist.size(); ++i)
+# 562 "kind.ag"
+    {
+# 563 "kind.ag"
+      FileName fn(dirlist[i]);
+# 564 "kind.ag"
+      if (startsWith(fn.getName(), imgname)) // dir is image ?
+# 565 "kind.ag"
+        {
+# 566 "kind.ag"
+          debugPrint(dirlist[i]);
+# 567 "kind.ag"
+
+# 568 "kind.ag"
+          DateTime t = imageDate(dirlist[i]);
+# 569 "kind.ag"
+
+# 570 "kind.ag"
+          if (t != now) // ignore just created image
+# 571 "kind.ag"
+            {
+# 572 "kind.ag"
+              if (!isValidImage(dirlist[i])) // invalid image?
+# 573 "kind.ag"
+                {
+# 574 "kind.ag"
+                  invalidImages.push_back(dirlist[i]);
+# 575 "kind.ag"
+                  debugPrint("- invalid image");
+# 576 "kind.ag"
+                }
+# 577 "kind.ag"
+              else
+# 578 "kind.ag"
+                {
+# 579 "kind.ag"
+                  validImages.push_back(dirlist[i]);
+# 580 "kind.ag"
+                  debugPrint("- valid image");
+# 581 "kind.ag"
+                }
+# 582 "kind.ag"
+            }
+# 583 "kind.ag"
+          else
+# 584 "kind.ag"
+            debugPrint("- current image - ignored");
+# 585 "kind.ag"
+        }
+# 586 "kind.ag"
+    }
+# 587 "kind.ag"
+
+# 588 "kind.ag"
+  for (unsigned int i = 0; i < invalidImages.size(); ++i)
+# 589 "kind.ag"
+    {
+# 590 "kind.ag"
+      try
+# 591 "kind.ag"
+        {
+# 592 "kind.ag"
+          DateTime t = imageDate(invalidImages[i]);
+# 593 "kind.ag"
+          DateTime expireTime = t + stot(conf.getString("expireFailedImage"));
+# 594 "kind.ag"
+          if (debug)
+# 595 "kind.ag"
+            {
+# 596 "kind.ag"
+              cout << "image: " << t.getString('h') << "  expire: " << expireTime.getString('h') << endl;
+# 597 "kind.ag"
+              cout << " now: " << now.getString('h') << endl;
+# 598 "kind.ag"
+            }
+# 599 "kind.ag"
+          if (expireTime < now)
+# 600 "kind.ag"
+            {
+# 601 "kind.ag"
+              if (!quiet)
+# 602 "kind.ag"
+                cout << "  removing invalid image " << invalidImages[i] << endl;
+# 603 "kind.ag"
+              if (removeDir(invalidImages[i]) != 0)
+# 604 "kind.ag"
+                cout << "Error removing " <<  invalidImages[i] << endl;
+# 605 "kind.ag"
+            }
+# 606 "kind.ag"
+        }
+# 607 "kind.ag"
+      catch (Exception ex)
+# 608 "kind.ag"
+        {
+# 609 "kind.ag"
+          cerr << "Exception: " << ex.what() << endl;
+# 610 "kind.ag"
+        }
+# 611 "kind.ag"
+    }
+# 612 "kind.ag"
+
+# 613 "kind.ag"
+  sort(validImages.begin(), validImages.end()); // lexicographical order == temporal order
+# 614 "kind.ag"
+  for (unsigned int i = 0;
+# 615 "kind.ag"
+  i < validImages.size() - 1; // never expire latest image
+# 616 "kind.ag"
+  ++i)
+# 617 "kind.ag"
+    {
+# 618 "kind.ag"
+      try
+# 619 "kind.ag"
+        {
+# 620 "kind.ag"
+          DateTime imageTime = imageDate(validImages[i]);
+# 621 "kind.ag"
+          DateTime expireTime = DateTime::now() + 100; // don't expire if  no rule found
+# 622 "kind.ag"
+          Strings expireRules = conf.getStrings("expireRule");
+# 623 "kind.ag"
+          int ruleNr = 0;
+# 624 "kind.ag"
+          for (unsigned int k = 0; k < expireRules.size(); ++k)
+# 625 "kind.ag"
+            {
+# 626 "kind.ag"
+              debugPrint("Checking rule " + expireRules[k]);
+# 627 "kind.ag"
+
+# 628 "kind.ag"
+              set<int> M, D, W, h;
+# 629 "kind.ag"
+              set<int> Y, m, s;
+# 630 "kind.ag"
+              time_t expirePeriod;
+# 631 "kind.ag"
+              parseRule(expireRules[k], M, D, W, h, expirePeriod);
+# 632 "kind.ag"
+              //    cout << M << " " << D << " " << W << " " << h << " " << expirePeriod << endl;
+# 633 "kind.ag"
+
+# 634 "kind.ag"
+              if (imageTime.match(Y, M, D, W, h, m, s))
+# 635 "kind.ag"
+                {
+# 636 "kind.ag"
+                  debugPrint("match");
+# 637 "kind.ag"
+                  expireTime = imageTime + expirePeriod;
+# 638 "kind.ag"
+                  ruleNr = k;
+# 639 "kind.ag"
+                }
+# 640 "kind.ag"
+            }
+# 641 "kind.ag"
+          if (debug)
+# 642 "kind.ag"
+            {
+# 643 "kind.ag"
+              cout << "image: " << imageTime.getString('h') << "  expire: " << expireTime.getString('h') << endl;
+# 644 "kind.ag"
+              cout << " now: " << now.getString('h') << endl;
+# 645 "kind.ag"
+            }
+# 646 "kind.ag"
+          if (now > expireTime)
+# 647 "kind.ag"
+            {
+# 648 "kind.ag"
+              if (!quiet)
+# 649 "kind.ag"
+                cout << "removing " << validImages[i] << " rule=" << expireRules[ruleNr] << endl;
+# 650 "kind.ag"
+              removeDir(validImages[i]);
+# 651 "kind.ag"
+            }
+# 652 "kind.ag"
+        }
+# 653 "kind.ag"
+      catch (Exception ex)
+# 654 "kind.ag"
+        {
+# 655 "kind.ag"
+          cerr << "Exception: " << ex.what() << endl;
+# 656 "kind.ag"
+        }
+# 657 "kind.ag"
+    }
+# 658 "kind.ag"
+}
+# 659 "kind.ag"
+
+# 660 "kind.ag"
+/*AppGen:Main*/
+string ag_programName;
+
+void usage()
+{
+  cout << ag_programName << " - archiving backup" << endl;
+  cout << "Aufruf:" << endl;
+  cout << ag_programName << " [<options>] vault_or_group " << endl;
+  cout << "  vault_or_group - Vault to backup" << endl;
+  cout << "Optionen:" << endl;
+  cout << "  -f --full          Force full image == initial backup (default: false)" << endl;
+  cout << "  -c --masterconfig  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 << "  -B --backuponly    Only backup/no expire (default: false)" << endl;
+  cout << "  -E --expireonly    Only expire/no backup (default: false)" << endl;
+  cout << "  -D --dryrun        Dry run (no real backup) (default: false)" << endl;
+  cout << "  -v --verbose       Verbose (default: false)" << endl;
+  cout << "  -d --debug         Debug output of many data (default: false)" << endl;
+  cout << "  -q --quiet         Be quiet - no messages (default: false)" << endl;
+  cout << "  -h --help          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)
+{
+  bool fullImage = false;
+  string masterConfig = "";
+  bool backupOnly = false;
+  bool expireOnly = false;
+
+  string vault = "";
+  static struct option ag_long_options[] =
+  {
+    {"full", no_argument, 0, 'f' },
+    {"masterconfig", required_argument, 0, 'c' },
+    {"backuponly", no_argument, 0, 'B' },
+    {"expireonly", no_argument, 0, 'E' },
+    {"dryrun", no_argument, 0, 'D' },
+    {"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, ":fc:BEDvdqh", ag_long_options, NULL)) >= 0)
+    {
+      switch (rc)
+        {
+        case '?':
+          error("Unknown option");
+          break;
+        case ':':
+          error("Expecting option parameter");
+          break;
+        case 'f':
+          fullImage = true;
+          break;
+
+        case 'c':
+          masterConfig = optarg;
+          break;
+
+        case 'B':
+          backupOnly = true;
+          break;
+
+        case 'E':
+          expireOnly = true;
+          break;
+
+        case 'D':
+          dryRun = true;
+          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*/
+# 664 "kind.ag"
+
+# 665 "kind.ag"
+  int exitCode = 0;
+# 666 "kind.ag"
+  string lockFile;
+# 667 "kind.ag"
+  try
+# 668 "kind.ag"
+    {
+# 669 "kind.ag"
+      if (debug)
+# 670 "kind.ag"
+        verbose = true;
+# 671 "kind.ag"
+
+# 672 "kind.ag"
+      KindConfig conf;
+# 673 "kind.ag"
+
+# 674 "kind.ag"
+      // default-values
+# 675 "kind.ag"
+      conf.add("imageName", "image");
+# 676 "kind.ag"
+      conf.add("vaultConfigName", "kind/vault.conf");
+# 677 "kind.ag"
+      conf.add("expireFailedImage", "3 days");
+# 678 "kind.ag"
+      conf.add("expireRule", "* * * * 1 month");
+# 679 "kind.ag"
+      conf.add("rsyncOption", ""); // no additional rsync option
+# 680 "kind.ag"
+      conf.add("remoteShell", "");
+# 681 "kind.ag"
+      conf.add("lockfile", "/var/lock/kind");
+# 682 "kind.ag"
+      conf.add("userExcludeFile", "nobackup.list");
+# 683 "kind.ag"
+      conf.add("userExcludeCommand",
+# 684 "kind.ag"
+               "find %path -type f -iname '*nobackup' -printf '%P\\\\n'");
+# 685 "kind.ag"
+
+# 686 "kind.ag"
+      conf.add("logSize", "");
+# 687 "kind.ag"
+
+# 688 "kind.ag"
+      if (!masterConfig.empty())
+# 689 "kind.ag"
+        readMasterConfig(masterConfig, conf);
+# 690 "kind.ag"
+      else if (fileExists("/etc/kind/master.conf"))
+# 691 "kind.ag"
+        readMasterConfig("etc/kind/master.conf", conf);
+# 692 "kind.ag"
+      else if (fileExists("/ffp/etc/kind/master.conf"))
+# 693 "kind.ag"
+        readMasterConfig("/ffp/etc/kind/master.conf", conf);
+# 694 "kind.ag"
+      else
+# 695 "kind.ag"
+        throw Exception("MasterConfig", "no file");
+# 696 "kind.ag"
+
+# 697 "kind.ag"
+      if (debug)
+# 698 "kind.ag"
+        {
+# 699 "kind.ag"
+          cout << "global config:" << endl;
+# 700 "kind.ag"
+          conf.print();
+# 701 "kind.ag"
+        }
+# 702 "kind.ag"
+
+# 703 "kind.ag"
+      lockFile = conf.getString("lockfile");
+# 704 "kind.ag"
+      createLock(lockFile);
+# 705 "kind.ag"
+
+# 706 "kind.ag"
+      DateTime imageTime = DateTime::now();
+# 707 "kind.ag"
+      string logSizeFile = conf.getString("logSize");
+# 708 "kind.ag"
+      if (!logSizeFile.empty() && fileExists(logSizeFile))
+# 709 "kind.ag"
+        {
+# 710 "kind.ag"
+          vector<string> ss;
+# 711 "kind.ag"
+          file2Strings(logSizeFile, ss);
+# 712 "kind.ag"
+          for (auto s : ss)
+# 713 "kind.ag"
+            {
+# 714 "kind.ag"
+              unsigned int i = 0;
+# 715 "kind.ag"
+              string v = getWord(s, i);
+# 716 "kind.ag"
+              long int s1 = getLongInt(s, i);
+# 717 "kind.ag"
+              long int s2 = getLongInt(s, i);
+# 718 "kind.ag"
+              sizes[v] = Sizes(s1, s2);
+# 719 "kind.ag"
+            }
+# 720 "kind.ag"
+        }
+# 721 "kind.ag"
+
+# 722 "kind.ag"
+      vector<string> vaults;
+# 723 "kind.ag"
+      string groupname = "group_" + vault;
+# 724 "kind.ag"
+      if (conf.hasKey(groupname))
+# 725 "kind.ag"
+        vaults = conf.getStrings(groupname);
+# 726 "kind.ag"
+      else
+# 727 "kind.ag"
+        vaults.push_back(vault);
+# 728 "kind.ag"
+
+# 729 "kind.ag"
+      if (!expireOnly)
+# 730 "kind.ag"
+        for (unsigned int i = 0; i < vaults.size(); ++i)
+# 731 "kind.ag"
+          {
+# 732 "kind.ag"
+            backupVault(vaults[i], conf, imageTime, fullImage);
+# 733 "kind.ag"
+            if (!logSizeFile.empty())
+# 734 "kind.ag"
+              {
+# 735 "kind.ag"
+                Strings st;
+# 736 "kind.ag"
+                for (auto s : sizes)
+# 737 "kind.ag"
+                  {
+# 738 "kind.ag"
+                    string h = s.first + " " + to_string(s.second.first) + " " + to_string(s.second.second);
+# 739 "kind.ag"
+                    st.push_back(h);
+# 740 "kind.ag"
+                  }
+# 741 "kind.ag"
+                strings2File(st, logSizeFile);
+# 742 "kind.ag"
+              }
+# 743 "kind.ag"
+          }
+# 744 "kind.ag"
+
+# 745 "kind.ag"
+      if (!backupOnly)
+# 746 "kind.ag"
+        for (unsigned int i = 0; i < vaults.size(); ++i)
+# 747 "kind.ag"
+          expireVault(vaults[i], conf, imageTime);
+# 748 "kind.ag"
+
+# 749 "kind.ag"
+      if (!quiet)
+# 750 "kind.ag"
+        cout << DateTime::now().getString('h') << ": finished" << endl;
+# 751 "kind.ag"
+
+# 752 "kind.ag"
+    }
+# 753 "kind.ag"
+  catch (const Exception& ex)
+# 754 "kind.ag"
+    {
+# 755 "kind.ag"
+      cerr << "Exception: " << ex.what() << endl;
+# 756 "kind.ag"
+      exitCode = 1;
+# 757 "kind.ag"
+    }
+# 758 "kind.ag"
+  catch (const char* msg)
+# 759 "kind.ag"
+    {
+# 760 "kind.ag"
+      cerr << "Exception(char*): " << msg << endl;
+# 761 "kind.ag"
+      exitCode = 1;
+# 762 "kind.ag"
+    }
+# 763 "kind.ag"
+  catch (const string& msg)
+# 764 "kind.ag"
+    {
+# 765 "kind.ag"
+      cerr << "Exception(string): " << msg << endl;
+# 766 "kind.ag"
+      exitCode = 1;
+# 767 "kind.ag"
+    }
+# 768 "kind.ag"
+  removeLock(lockFile);
+# 769 "kind.ag"
+  return exitCode;
+# 770 "kind.ag"
+}

+ 117 - 0
rulecomp.cpp

@@ -0,0 +1,117 @@
+#include <iostream>
+#include <set>
+#include <vector>
+#include <algorithm>
+
+#include "Lexer.h"
+#include "rulecomp.h"
+
+using namespace std;
+
+vector<vector<string> > weekDayString
+{
+  {"sunday", "sun"},
+  {"monday", "mon"},
+  {"tuesday", "tue"},
+  {"wednesday", "wed"},
+  {"thursday", "thu"},
+  {"friday", "fri"},
+  {"saturday", "sat"}
+};
+
+vector<vector<string> > monthString
+{
+  {"dummy"},
+  {"january", "jan"},
+  {"febrary", "feb"},
+  {"march", "mar"},
+  {"april", "apr"},
+  {"may"},
+  {"june", "jun"},
+  {"july", "jul"},
+  {"august", "aug"},
+  {"september", "sep"},
+  {"october", "oct"},
+  {"november", "nov"},
+  {"december", "dec"}
+};
+
+int getNr(const string& w, const vector<vector<string>>& dict)
+{
+  int res = -1;
+  for (unsigned int i = 0; res < 0 && i < dict.size(); ++i)
+    {
+      const vector<string>& entry = dict[i];
+      if (find(entry.begin(), entry.end(), w) != entry.end())
+        res = i;
+    }
+  if (res < 0)
+    throw Exception("Range", "unknown identifier: " + w);
+  return res;
+}
+
+int getNr(Lexer& p, int altstring)
+{
+  if (p.type == Lexer::integer)
+    return p.getInt();
+  else if (altstring != 0 && p.type == Lexer::identifier)
+    {
+      string w = p.getWord();
+      int res = getNr(w, altstring == 1 ? weekDayString : monthString);
+      return res;
+    }
+  else
+    throw Exception("getNr", "int or identifier expected");
+}
+
+std::set<int> getValues(Lexer& p, int first, int last, const int altstring)
+{
+  int mod = 1;
+  set<int> values;
+
+  if (p.token == "*")
+    {
+      p.nextToken();
+      if (p.token == "/")
+        {
+          p.nextToken();
+          mod = p.getInt();
+          if (mod <= 0)
+            throw Exception("Range", string("Modulo value invalid ") + to_string(mod));
+        }
+      for (int i = first; i <= last; i += mod)
+        values.insert(i);
+    }
+  else
+    {
+      int s = getNr(p, altstring);
+      if (s < first || s > last)
+        throw Exception("Range", "value out of range");
+      if (p.token != "-")
+        values.insert(s);
+      else
+        {
+          p.nextToken();
+          // cout << p.token << endl;
+          int e =  getNr(p, altstring);
+          if (e < s || e > last)
+            throw Exception("Range", "end value out of range");
+          if (p.token == "/")
+            {
+              p.nextToken();
+              mod = p.getInt();
+            }
+          for (int i = s; i <= e; i += mod)
+            values.insert(i);
+        }
+    }
+  return values;
+}
+
+set<int> getValues(const string& range, int first, int last, int altstringmode) // range is string without spaces
+{
+  if (range.empty())
+    throw Exception("Range", "Description is empty");
+  Lexer p(range);
+  return getValues(p, first, last, altstringmode);
+}

+ 12 - 0
rulecomp.h

@@ -0,0 +1,12 @@
+#ifndef RULECOMP_H
+#define RULECOMP_H
+
+#include <set>
+#include <string>
+#include "Lexer.h"
+
+std::set<int> getValues(const std::string& range, int firstvalue, int lastvalue, int altstringmode = 0);
+
+std::set<int> getValues(Lexer& p, int firstvalue, int lastvalue, int altstringmode = 0);
+
+#endif

+ 195 - 0
stringtools.cpp

@@ -0,0 +1,195 @@
+#include <iostream>
+#include "Exception.h"
+#include "stringtools.h"
+
+using namespace std;
+
+string delSpaces(const string& s)
+{
+  string res = "";
+  for (unsigned int i = 0; i < s.size(); ++i)
+    {
+      if (!isspace(s[i]))
+        res += s[i];
+    }
+  return res;
+}
+
+void substitute(std::string& s, char c1, char c2)
+{
+  for (unsigned int i = 0; i < s.size(); ++i)
+    if (s[i] == c1) s[i] = c2;
+}
+
+void reduceToOne(std::string& s, char c)
+{
+  unsigned int pr = 0;
+  unsigned int pw = 0;
+  while (pr < s.size())
+    {
+      char ac = s[pr];
+      s[pw] = ac;
+      ++pr;
+      ++pw;
+      if (ac == c)
+        {
+          while (pr < s.size() && s[pr] == c) // skip further characters c
+            ++pr;
+        }
+    }
+  s.resize(pw);
+}
+
+string trim(const string& s)
+{
+  string res = "";
+
+  int last = s.size() - 1;
+  while (last >= 0 && isspace(s[last]))
+    --last;
+
+  int first = 0;
+  while (first < (int)s.size() && isspace(s[first]))
+    ++first;
+
+  for (int i = first; i <= last; ++i)
+    res += s[i];
+  return res;
+}
+
+bool startsWith(const string& s, const string& start)
+{
+  int len = start.length();
+  return s.substr(0, len) == start;
+}
+
+int split(const string& s, Strings& parts, char del, int expectedParts)
+{
+  parts.clear();
+  string p = "";
+  if (!s.empty())
+    {
+      for (unsigned int i = 0; i < s.size(); ++i)
+        {
+          if (s[i] == del)
+            {
+              parts.push_back(p);
+              p.clear();
+            }
+          else
+            p += s[i];
+        }
+      parts.push_back(p);
+    }
+  if (expectedParts > 0 && expectedParts != (int)parts.size())
+    throw Exception("split", string("Unexpected number of parts: ") + s);
+  return parts.size();
+}
+
+void skipWS(const string& s, unsigned  int& i)
+{
+  static const string ws = " \t\n";
+  while (i < s.size() && ws.find(s[i]) != string::npos)
+    ++i;
+}
+
+int getInt(const string& s, unsigned int& i)
+{
+  if (!isdigit(s[i]))
+    throw Exception("getInt", "digit expected");
+  string is;
+  while (i < s.size() && isdigit(s[i]))
+    {
+      is += s[i];
+      ++i;
+    }
+  int res = stoi(is);
+  skipWS(s, i);
+  return res;
+}
+
+long int getLongInt(const string& s, unsigned int& i)
+{
+  if (!isdigit(s[i]))
+    throw Exception("getLongInt", "digit expected");
+  string is;
+  while (i < s.size() && isdigit(s[i]))
+    {
+      is += s[i];
+      ++i;
+    }
+  long int res = stol(is);
+  skipWS(s, i);
+  return res;
+}
+
+string getWord(const string& s, unsigned int& i)
+{
+  if (!isalpha(s[i]) && s[i] != '-' && s[i] != '_')
+    throw Exception("getWord", "letter expected");
+  string is;
+  while (i < s.size() && (isalpha(s[i]) || s[i] == '-' || s[i] == '_'))
+    {
+      is += s[i];
+      ++i;
+    }
+  skipWS(s, i);
+  return is;
+}
+
+time_t stot(const string& s)
+{
+  unsigned int i = 0;
+  skipWS(s, i);
+  int val = getInt(s, i);
+  if (i >= s.length())
+    return val;
+  string unit = getWord(s, i);
+  if (unit.back() != 's')
+    unit += 's';
+  if (unit == "secs")
+    return val;
+  val *= 60;
+  if (unit == "mins")
+    return val;
+  val *= 60;
+  if (unit == "hours")
+    return val;
+  val *= 24;
+  if (unit == "days")
+    return val;
+  if (unit == "weeks")
+    return val * 7;
+  val *= 30;
+  if (unit == "months")
+    return val;
+  val *= 12;
+  if (unit != "years")
+    throw Exception("Parse time", "unknown time unit " + unit);
+  return val;
+}
+
+long int getNumber(const string& l)
+{
+  // read *all* digits from string l ignoring all other characters
+  string d;
+  for (unsigned int i = 0; i < l.size(); ++i)
+    if (isdigit(l[i]))
+      d += l[i];
+  // return long int value of digits
+  return stol(d);
+}
+
+void replacePlaceHolder(string& s,
+                        const string& placeholder,
+                        const string& content)
+{
+  int psize = placeholder.size();
+  size_t pos = s.find(placeholder);
+  while (pos != string::npos)
+    {
+      s = s.substr(0, pos) + content + s.substr(pos + psize);
+      pos = s.find(placeholder);
+    }
+}
+

+ 38 - 0
stringtools.h

@@ -0,0 +1,38 @@
+#ifndef STRING_TOOLS_H
+#define STRING_TOOLS_H
+
+#include <string>
+#include <vector>
+
+#include "Strings.h"
+//typedef std::vector<std::string> Strings;
+
+// simple string manipulations
+std::string delSpaces(const std::string& s);
+std::string trim(const std::string& s);
+
+void reduceToOne(std::string& s, char c);
+void substitute(std::string& s, char c1, char c2);
+
+// split in parts
+int split(const std::string& s, Strings& parts,
+          char del, int expectedParts = 0);
+
+// string parsing
+void skipWS(const std::string& s, unsigned  int& i);
+int getInt(const std::string& s, unsigned int& i);
+long int getLongInt(const std::string& s, unsigned int& i);
+std::string getWord(const std::string& s, unsigned int& i);
+
+long int getNumber(const std::string& l);
+
+bool startsWith(const std::string& s, const std::string& start);
+
+// string to time
+time_t stot(const std::string& s);
+
+void replacePlaceHolder(std::string& format,
+                        const std::string& placeholder,
+                        const std::string& content);
+
+#endif