New version of identity management. Using general csv file, so any number of fields...
authorFrantišek Dvořák <valtri@civ.zcu.cz>
Sat, 19 Oct 2013 19:17:29 +0000 (21:17 +0200)
committerFrantišek Dvořák <valtri@civ.zcu.cz>
Fri, 21 Feb 2014 13:36:47 +0000 (14:36 +0100)
src/VfsAuthn.cpp
src/VfsAuthn.h

index 0bb916b..3d65e0a 100644 (file)
@@ -27,7 +27,7 @@ VfsAuthn::VfsAuthn(const std::string& prefix) throw (DmException)
   this->dirtyUsers_ = false;
   this->dirtyGroups_ = false;
 
-  vfsReload();
+  vfsLoad();
 }
 
 
@@ -327,134 +327,229 @@ void VfsAuthn::deleteUser(const std::string& userName) throw (DmException)
 
 
 
-void VfsAuthn::vfsReload() throw (DmException) {
-  std::string entrypp;
-  std::ifstream *f;
-  const char *entry;
-  unsigned int uid, gid, banned;
-  int pos, ret, n;
+static void csv_users_load_entry(void *ctx, const Extensible& values) {
+  std::vector<UserInfo> *uis = (std::vector<UserInfo> *)ctx;
   UserInfo ui;
+
+  ui.name = values.getString("name");
+  ui.copy(values);
+  ui.erase("name");
+  uis->push_back(ui);
+}
+
+
+
+static void csv_groups_load_entry(void *ctx, const Extensible& values) {
+  std::vector<GroupInfo> *gis = (std::vector<GroupInfo> *)ctx;
   GroupInfo gi;
 
+  gi.name = values.getString("name");
+  gi.copy(values);
+  gi.erase("name");
+  gis->push_back(gi);
+}
+
+
+
+void VfsAuthn::vfsLoad() throw (DmException) {
+  // users
   this->users_.clear();
   this->uids_.clear();
-  f = new std::ifstream((this->prefix_ + VFS_USERS_FILE).c_str());
-  if (f->is_open()) {
-    n = 0;
-    while (std::getline(*f, entrypp)) {
-      n++;
-      entry = entrypp.c_str();
-      ret = sscanf(entry, "%u%*[ \t]%u%*[ \t]%n", &uid, &banned, &pos);
-      if (ret != 2) {
-        f->close();
-        delete f;
-        vfsThrow(ret == EOF ? DMLITE_SYSERR(EINVAL) : errno, "parsing users file failed at line %d (%s)", n, entry);
-      }
-      //printf("entry: %u %u '%s'\n", uid, banned, entry + pos);
-
-      ui.clear();
-      ui.name = entry + pos;
-      ui["uid"] = uid;
-      ui["banned"] = banned ? 1 : 0;
-      this->users_.push_back(ui);
-      // no check - we're OK with multiple users on the same uid
-      this->uids_.insert(uid);
+  try {
+    csvLoad(VFS_USERS_FILE, (void *)&this->users_, this->userFields_, csv_users_load_entry);
+  } catch (DmException& e) {
+    switch (e.code()) {
+      case DMLITE_SYSERR(EIO):
+        break;
+      default:
+        vfsThrow(e.code(), std::string("error parsing users file ") + e.what());
+        break;
     }
-    f->close();
   }
-  delete f;
+  for (size_t i = 0; i < this->users_.size(); i++)
+    this->uids_.insert(this->users_[i].getUnsigned("uid"));
 
+  // groups
   this->groups_.clear();
   this->gids_.clear();
-  f = new std::ifstream((this->prefix_ + VFS_GROUPS_FILE).c_str());
-  if (f->is_open()) {
-    n = 0;
-    while (std::getline(*f, entrypp)) {
-      n++;
-      entry = entrypp.c_str();
-      ret = sscanf(entry, "%u%*[ \t]%u%*[ \t]%n", &gid, &banned, &pos);
-      if (ret != 2) {
-        f->close();
-        delete f;
-        vfsThrow(ret == EOF ? DMLITE_SYSERR(EINVAL) : errno, "parsing groups file failed at line %d (%s)", n, entry);
-      }
-      //printf("entry: %u %u '%s'\n", gid, banned, entry + pos);
-
-      gi.clear();
-      gi.name = entry + pos;
-      gi["gid"] = gid;
-      gi["banned"] = banned ? 1 : 0;
-      this->groups_.push_back(gi);
-      // no check - we're OK with multiple groups on the same gid
-      this->gids_.insert(gid);
+  try {
+    csvLoad(VFS_GROUPS_FILE, (void *)&this->groups_, this->groupFields_, csv_groups_load_entry);
+  } catch (DmException& e) {
+    switch (e.code()) {
+      case DMLITE_SYSERR(EIO):
+        break;
+      default:
+        vfsThrow(e.code(), std::string("error parsing groups file ") + e.what());
+        break;
     }
-    f->close();
   }
-  delete f;
+  for (size_t i = 0; i < this->groups_.size(); i++)
+    this->gids_.insert(this->groups_[i].getUnsigned("gid"));
 }
 
 
 
-void VfsAuthn::vfsSaveUsers() throw (DmException) {
-  std::ofstream f;
-  std::string newName, name;
+static bool csv_users_save_entry(void *ctx, size_t n, Extensible& values) {
+       std::vector<UserInfo> *uis = (std::vector<UserInfo> *)ctx;
+
+       if (n < uis->size()) {
+               values.copy((*uis)[n]);
+               values["name"] = (*uis)[n].name;
+               return true;
+       }
+
+       return false;
+}
+
 
+
+void VfsAuthn::vfsSaveUsers() throw (DmException) {
   if (this->noSync_) {
     this->dirtyUsers_ = true;
     return;
   }
 
-  name = this->prefix_ + VFS_USERS_FILE;
-  newName = this->prefix_ + ".new" + VFS_USERS_FILE;
-
-  f.open(newName.c_str());
-  if (!f) {
+  try {
+    csvSave(VFS_USERS_FILE, (void *)&this->users_, this->userFields_, csv_users_save_entry);
+  } catch (DmException& e) {
     log(LOG_NOTICE, "could not create new version of users file in '%s'", this->prefix_.c_str());
     this->dirtyUsers_ = true;
     return;
-   }
-
-  for (size_t i = 0; i < this->users_.size(); i++) {
-    f << this->users_[i].getUnsigned("uid") << "\t";
-    f << this->users_[i].getUnsigned("banned") << "\t";
-    f << this->users_[i].name << "\n";
   }
 
-  f.close();
-  wrapCall(rename(newName.c_str(), name.c_str()), "could not save users file");
-
   this->dirtyUsers_ = false;
 }
 
 
 
-void VfsAuthn::vfsSaveGroups() throw (DmException) {
-  std::ofstream f;
-  std::string newName, name;
+static bool csv_groups_save_entry(void *ctx, size_t n, Extensible& values) {
+       std::vector<GroupInfo> *gis = (std::vector<GroupInfo> *)ctx;
+
+       if (n < gis->size()) {
+               values.copy((*gis)[n]);
+               values["name"] = (*gis)[n].name;
+               return true;
+       }
+
+       return false;
+}
 
+
+
+void VfsAuthn::vfsSaveGroups() throw (DmException) {
   if (this->noSync_) {
     this->dirtyGroups_ = true;
     return;
   }
 
-  name = this->prefix_ + VFS_GROUPS_FILE;
-  newName = this->prefix_ + ".new" + VFS_GROUPS_FILE;
-
-  f.open(newName.c_str());
-  if (!f) {
+  try {
+    csvSave(VFS_GROUPS_FILE, (void *)&this->groups_, this->groupFields_, csv_groups_save_entry);
+  } catch (DmException& e) {
     log(LOG_NOTICE, "could not create new version of groups file in '%s'", this->prefix_.c_str());
     this->dirtyGroups_ = true;
     return;
   }
 
-  for (size_t i = 0; i < this->groups_.size(); i++) {
-    f << this->groups_[i].getUnsigned("gid") << "\t";
-    f << this->groups_[i].getUnsigned("banned") << "\t";
-    f << this->groups_[i].name << "\n";
+  this->dirtyGroups_ = false;
+}
+
+
+
+void VfsAuthn::csvLoad(std::string fileName, void *ctx, std::set<std::string>& keySet, csv_load_entry_f *callback) throw (DmException) {
+  std::ifstream f;
+  std::string line;
+  size_t i, n;
+  char *s, *value, *ptr;
+  std::vector<std::string> keys;
+  dmlite::Extensible values;
+
+  f.open((this->prefix_ + fileName).c_str());
+  if (!f)
+    throw DmException(DMLITE_SYSERR(EIO), "can't open file");
+
+  // header
+  if (!std::getline(f, line))
+    return;
+  ptr = s = strdup(line.c_str());
+  value = strsep(&ptr, "\t");
+  while (value) {
+    keys.push_back(value);
+    value = strsep(&ptr, "\t");
+  }
+  free(s);
+
+  // name column always in the end
+  if (keys.back() != "name")
+    throw DmException(DMLITE_SYSERR(EINVAL), "at line 1 - missing 'name' column (%s)", line.c_str());
+
+  keySet.clear();
+  for (size_t i = 0; i < keys.size(); i++)
+    keySet.insert(keys[i]);
+
+  // data
+  n = 1;
+  while (std::getline(f, line)) {
+    n++;
+
+    values.clear();
+    ptr = s = strdup(line.c_str());
+    value = strsep(&ptr, "\t");
+    for (i = 0; i < keys.size() && value; i++) {
+      if (value[0] != '\0')
+        values[keys[i]] = std::string(value);
+      value = strsep(&ptr, "\t");
+    }
+    free(s);
+
+    if (i != keys.size())
+      throw DmException(DMLITE_SYSERR(EINVAL), "at line %d - missing columns (%s)", n, line.c_str());
+
+    (*callback)(ctx, values);
   }
+}
 
-  f.close();
-  wrapCall(rename(newName.c_str(), name.c_str()), "could not save groups file");
 
-  this->dirtyGroups_ = false;
+
+void VfsAuthn::csvSave(std::string fileName, void *ctx, std::set<std::string>& keySet, csv_save_entry_f *callback) throw (DmException) {
+  std::ofstream f;
+  std::string newPath, path;
+  size_t n;
+  std::vector<std::string> keys;
+  dmlite::Extensible values;
+
+  path = this->prefix_ + fileName;
+  newPath = this->prefix_ + ".new" + fileName;
+
+  f.open(newPath.c_str());
+  if (!f)
+    throw DmException(DMLITE_SYSERR(EIO), "could create file '%s'", newPath.c_str());
+
+  // name column always in the end
+  for (std::set<std::string>::iterator it = keySet.begin(); it != keySet.end(); it++)
+    if (*it == "name") continue;
+    else keys.push_back(*it);
+  keys.push_back("name");
+
+  // header
+  for (size_t i = 0; i < keys.size(); i++) {
+    if (i) f << "\t";
+    f << keys[i];
+  }
+  f << std::endl;
+
+  // data
+  n = 0;
+  while ((*callback)(ctx, n, values)) {
+    for (size_t i = 0; i < keys.size(); i++) {
+      if (i) f << "\t";
+      f << values.getString(keys[i]);
+    }
+    f << std::endl;
+    values.clear();
+    n++;
+  }
+
+  f.close();
+  if (rename(newPath.c_str(), path.c_str()) == -1)
+    throw DmException(errno, "could not save file '%s'", path.c_str());
 }
index 8e7cf24..e557e3c 100644 (file)
@@ -8,6 +8,9 @@
 
 namespace dmlite {
 
+  typedef void (csv_load_entry_f)(void *ctx, const dmlite::Extensible&);
+  typedef bool (csv_save_entry_f)(void *ctx, size_t n, dmlite::Extensible&);
+
   class VfsAuthn: public Authn
   {
     public:
@@ -37,17 +40,20 @@ namespace dmlite {
     protected:
       void vfsNewGroup(const std::string& groupName) throw (DmException);
       void vfsNewUser(const std::string& userName) throw (DmException);
-      void vfsReload() throw (DmException);
+      void vfsLoad() throw (DmException);
       void vfsSaveGroups() throw (DmException);
       void vfsSaveUsers() throw (DmException);
+      void csvLoad(std::string fileName, void *ctx, std::set<std::string>& keys, csv_load_entry_f *callback) throw (DmException);
+      void csvSave(std::string fileName, void *ctx, std::set<std::string>& keys, csv_save_entry_f *callback) throw (DmException);
 
-      std::string prefix_;
+      std::string prefix_;                   /// namespace local disk path prefix
       std::vector<UserInfo> users_;
       std::vector<GroupInfo> groups_;
       std::set<unsigned int> uids_, gids_;   /// helper sets with UID/GID
+      std::set<std::string> userFields_, groupFields_; /// all fields used in user/group info
       unsigned int nextUid_, nextGid_;       /// next UID/GID after the one last inserted
       bool noSync_;                          /// saving users/groups disabled
-      bool dirtyUsers_, dirtyGroups_;        /// data which need to be saved
+      bool dirtyUsers_, dirtyGroups_;        /// data needed to be saved
   };
 
 };