fix long shutdown delay after Ctrl-C
Stefan Schuermans

Stefan Schuermans commited on 2020-09-19 13:55:21
Showing 10 changed files, with 139 additions and 49 deletions.

... ...
@@ -1,6 +1,7 @@
1 1
 add_library(
2 2
   permissioner
3 3
   STATIC
4
+  include/permissioner/Callback.h
4 5
   include/permissioner/Config.h
5 6
   include/permissioner/Group.h
6 7
   include/permissioner/Nice.h
... ...
@@ -8,6 +9,7 @@ add_library(
8 9
   include/permissioner/StringUtils.h
9 10
   include/permissioner/Tree.h
10 11
   include/permissioner/User.h
12
+  src/Callback.cpp
11 13
   src/Config.cpp
12 14
   src/Group.cpp
13 15
   src/Nice.cpp
... ...
@@ -0,0 +1,24 @@
1
+/**
2
+ * Permissioner: set file ownerships and permissions
3
+ * Copyright 2020: Stefan Schuermans, Aachen, Germany <stefan@schuermans.info>
4
+ * Copyleft: GNU GENERAL PUBLIC LICENSE version 3 (see LICENSE)
5
+ */
6
+
7
+#ifndef CALLBACK_H
8
+#define CALLBACK_H
9
+
10
+/// callback interface, called after each file
11
+class Callback {
12
+public:
13
+  virtual ~Callback();
14
+
15
+  /**
16
+   * @brief callback, called after each file
17
+   * @return whether to continue (false means abort)
18
+   *
19
+   * This default implementation does nothing and returns true
20
+   */
21
+  virtual bool callback();
22
+};
23
+
24
+#endif // #ifndef CALLBACK_H
... ...
@@ -7,6 +7,7 @@
7 7
 #ifndef CONFIG_H
8 8
 #define CONFIG_H
9 9
 
10
+#include <permissioner/Callback.h>
10 11
 #include <permissioner/Nice.h>
11 12
 #include <permissioner/Tree.h>
12 13
 
... ...
@@ -32,8 +33,10 @@ public:
32 33
 
33 34
   /**
34 35
    * @brief set owners and permissions of files in trees
36
+   * @param[in] callback callback object to call after each processed file
37
+   * @return whether traversal was completed (false means aborted)
35 38
    */
36
-  void setPermissions() const;
39
+  bool setPermissions(Callback &callback) const;
37 40
 
38 41
 protected:
39 42
   Nice nice;
... ...
@@ -7,6 +7,7 @@
7 7
 #ifndef TREE_H
8 8
 #define TREE_H
9 9
 
10
+#include <permissioner/Callback.h>
10 11
 #include <permissioner/Group.h>
11 12
 #include <permissioner/Permissions.h>
12 13
 #include <permissioner/User.h>
... ...
@@ -43,23 +44,31 @@ public:
43 44
   /**
44 45
    * @brief set owners and permissions of files in tree
45 46
    * @param[in] exclude map of other trees that shall be excluded
47
+   * @param[in] callback callback object to call after each processed file
48
+   * @return whether traversal was completed (false means aborted)
46 49
    */
47
-  void setPermissions(TreeMap const &exclude) const;
50
+  bool setPermissions(TreeMap const &exclude, Callback &callback) const;
48 51
 
49 52
 protected:
50 53
   /**
51 54
    * @brief set owners and permissions of files in path and subtrees
52 55
    * @param[in] path path to top of directory tree for which to set owner/perms
53 56
    * @param[in] exclude map of other trees that shall be excluded
57
+   * @param[in] callback callback object to call after each processed file
58
+   * @return whether to continue (false means abort traversal)
54 59
    */
55
-  void setPermissionsInternal(boost::filesystem::path const &path,
56
-                              TreeMap const &exclude) const;
60
+  bool setPermissionsInternal(boost::filesystem::path const &path,
61
+                              TreeMap const &exclude,
62
+                              Callback &callback) const;
57 63
 
58 64
   /**
59 65
    * @brief set owners and permissions of one file or directory
60 66
    * @param[in] path path to file or directory
67
+   * @param[in] callback callback object to call after each processed file
68
+   * @return whether to continue (false means abort traversal)
61 69
    */
62
-  void setPermissionsOne(boost::filesystem::path const &path) const;
70
+  bool setPermissionsOne(boost::filesystem::path const &path,
71
+                         Callback &callback) const;
63 72
 
64 73
 protected:
65 74
   User user;
... ...
@@ -0,0 +1,11 @@
1
+/**
2
+ * Permissioner: set file ownerships and permissions
3
+ * Copyright 2020: Stefan Schuermans, Aachen, Germany <stefan@schuermans.info>
4
+ * Copyleft: GNU GENERAL PUBLIC LICENSE version 3 (see LICENSE)
5
+ */
6
+
7
+#include <permissioner/Callback.h>
8
+
9
+Callback::~Callback() {}
10
+
11
+bool Callback::callback() { return true; }
... ...
@@ -5,8 +5,9 @@
5 5
  */
6 6
 
7 7
 #include <permissioner/Config.h>
8
-#include <permissioner/StringUtils.h>
9 8
 
9
+#include <permissioner/Callback.h>
10
+#include <permissioner/StringUtils.h>
10 11
 #include <permissioner/Tree.h>
11 12
 
12 13
 #include <boost/filesystem.hpp>
... ...
@@ -61,9 +62,12 @@ TreeMap const & Config::getTrees() const {
61 62
   return trees;
62 63
 }
63 64
 
64
-void Config::setPermissions() const {
65
+bool Config::setPermissions(Callback &callback) const {
65 66
   for (auto const & path_tree : trees) {
66 67
     Tree const & tree = path_tree.second;
67
-    tree.setPermissions(trees); // exclude all other trees
68
+    if (! tree.setPermissions(trees, callback)) { // exclude all other trees
69
+      return false;
70
+    }
68 71
   }
72
+  return true;
69 73
 }
... ...
@@ -6,10 +6,11 @@
6 6
 
7 7
 #include <permissioner/Tree.h>
8 8
 
9
+#include <permissioner/Callback.h>
9 10
 #include <permissioner/Group.h>
10 11
 #include <permissioner/Permissions.h>
11
-#include <permissioner/User.h>
12 12
 #include <permissioner/StringUtils.h>
13
+#include <permissioner/User.h>
13 14
 
14 15
 #include <boost/filesystem.hpp>
15 16
 #include <sstream>
... ...
@@ -38,8 +39,8 @@ void Tree::parseParams(std::string const &paramStr) {
38 39
     user.parseUserName(userStr);
39 40
   } catch (std::exception const &e) {
40 41
     std::stringstream msg;
41
-    msg << "invalid <user> field \"" << userStr << "\" in \""
42
-        << paramStr << "\": " << e.what();
42
+    msg << "invalid <user> field \"" << userStr << "\" in \"" << paramStr
43
+        << "\": " << e.what();
43 44
     throw std::runtime_error(msg.str());
44 45
   }
45 46
 
... ...
@@ -47,8 +48,8 @@ void Tree::parseParams(std::string const &paramStr) {
47 48
     group.parseGroupName(groupStr);
48 49
   } catch (std::exception const &e) {
49 50
     std::stringstream msg;
50
-    msg << "invalid <group> field \"" << groupStr << "\" in \""
51
-        << paramStr << "\": " << e.what();
51
+    msg << "invalid <group> field \"" << groupStr << "\" in \"" << paramStr
52
+        << "\": " << e.what();
52 53
     throw std::runtime_error(msg.str());
53 54
   }
54 55
 
... ...
@@ -65,54 +66,57 @@ void Tree::parseParams(std::string const &paramStr) {
65 66
     root = boost::filesystem::canonical(rootStr);
66 67
   } catch (std::exception const &e) {
67 68
     std::stringstream msg;
68
-    msg << "invalid <root> field \"" << rootStr << "\" in \""
69
-        << paramStr << "\": " << e.what();
69
+    msg << "invalid <root> field \"" << rootStr << "\" in \"" << paramStr
70
+        << "\": " << e.what();
70 71
     throw std::runtime_error(msg.str());
71 72
   }
72 73
 }
73 74
 
74
-User const & Tree::getUser() const {
75
-  return user;
76
-}
75
+User const &Tree::getUser() const { return user; }
77 76
 
78
-Group const & Tree::getGroup() const {
79
-  return group;
80
-}
77
+Group const &Tree::getGroup() const { return group; }
81 78
 
82
-Permissions const & Tree::getPermissions() const {
83
-  return permissions;
84
-}
79
+Permissions const &Tree::getPermissions() const { return permissions; }
85 80
 
86
-boost::filesystem::path const & Tree::getRoot() const {
87
-  return root;
88
-}
81
+boost::filesystem::path const &Tree::getRoot() const { return root; }
89 82
 
90
-void Tree::setPermissions(TreeMap const &exclude) const {
91
-  setPermissionsInternal(root, exclude);
83
+bool Tree::setPermissions(TreeMap const &exclude,
84
+                          Callback &callback) const {
85
+  return setPermissionsInternal(root, exclude, callback);
92 86
 }
93 87
 
94
-void Tree::setPermissionsInternal(boost::filesystem::path const &path,
95
-                                  TreeMap const &exclude) const {
88
+bool Tree::setPermissionsInternal(boost::filesystem::path const &path,
89
+                                  TreeMap const &exclude,
90
+                                  Callback &callback) const {
96 91
   try {
97 92
     if (boost::filesystem::is_regular_file(path)) {
98
-      setPermissionsOne(path);
93
+      if (!setPermissionsOne(path, callback)) {
94
+        return false;
95
+      };
99 96
     } else if (boost::filesystem::is_directory(path)) {
100
-      setPermissionsOne(path);
97
+      if (!setPermissionsOne(path, callback)) {
98
+        return false;
99
+      };
101 100
       for (boost::filesystem::directory_entry entry :
102 101
            boost::filesystem::directory_iterator(path)) {
103 102
         if (exclude.find(entry) != exclude.end()) {
104 103
           continue; // other tree -> skip here
105 104
         }
106
-        setPermissionsInternal(entry, exclude); // recurse
105
+        // recurse
106
+        if (!setPermissionsInternal(entry, exclude, callback)) {
107
+          return false;
108
+        }
107 109
       }
108 110
     }
109 111
   } catch (boost::filesystem::filesystem_error const &e) {
110 112
     // ignore filesystem errors for now, as this runs in a daemon in background
111 113
     (void)e;
112 114
   }
115
+  return true;
113 116
 }
114 117
 
115
-void Tree::setPermissionsOne(boost::filesystem::path const &path) const {
118
+bool Tree::setPermissionsOne(boost::filesystem::path const &path,
119
+                             Callback &callback) const {
116 120
   // change permissions
117 121
   try {
118 122
     permissions.apply(path);
... ...
@@ -125,4 +129,7 @@ void Tree::setPermissionsOne(boost::filesystem::path const &path) const {
125 129
   int ret = lchown(path.string().c_str(), user.getUid(), group.getGid());
126 130
   // ignore error for now, as this runs in a daemon in background
127 131
   (void)ret;
132
+
133
+  // call callback and return whether to continue
134
+  return callback.callback();
128 135
 }
... ...
@@ -4,6 +4,7 @@
4 4
  * Copyleft: GNU GENERAL PUBLIC LICENSE version 3 (see LICENSE)
5 5
  */
6 6
 
7
+#include <permissioner/Callback.h>
7 8
 #include <permissioner/Config.h>
8 9
 
9 10
 #include <cstdlib>
... ...
@@ -21,7 +22,8 @@ int main(int argc, char const **argv) {
21 22
   try {
22 23
     Config config;
23 24
     config.parseFile(configFileName);
24
-    config.setPermissions();
25
+    Callback callback;
26
+    config.setPermissions(callback);
25 27
   } catch (std::exception const &e) {
26 28
     std::cerr << "error: " << e.what() << std::endl;
27 29
     return EXIT_FAILURE;
... ...
@@ -4,6 +4,7 @@
4 4
  * Copyleft: GNU GENERAL PUBLIC LICENSE version 3 (see LICENSE)
5 5
  */
6 6
 
7
+#include <permissioner/Callback.h>
7 8
 #include <permissioner/Config.h>
8 9
 
9 10
 #include <chrono>
... ...
@@ -14,10 +15,32 @@
14 15
 #include <string>
15 16
 #include <thread>
16 17
 
17
-static int go_on = 1;
18
+class DaemonCallback : public Callback {
19
+public:
20
+  DaemonCallback() : go_on(true) {}
21
+  bool callback() { return go_on; }
22
+  bool go_on;
23
+};
18 24
 
19
-void sighandler(int) {
20
-  go_on = 0;
25
+DaemonCallback daemonCallback;
26
+
27
+void sighandler(int) { daemonCallback.go_on = false; }
28
+
29
+// iterative sleep, watching go_on, returns whether sleep completed
30
+template <class Rep, class Period>
31
+bool iterativeSleep(std::chrono::duration<Rep, Period> duration,
32
+                    DaemonCallback &daemonCallback) {
33
+  std::chrono::duration<int, std::milli> zero(0), step(100);
34
+  while (duration > zero && daemonCallback.go_on) {
35
+    if (duration >= step) {
36
+      std::this_thread::sleep_for(step);
37
+      duration -= step;
38
+    } else {
39
+      std::this_thread::sleep_for(duration);
40
+      duration = zero;
41
+    }
42
+  }
43
+  return duration <= zero;
21 44
 }
22 45
 
23 46
 int main(int argc, char const **argv) {
... ...
@@ -42,22 +65,23 @@ int main(int argc, char const **argv) {
42 65
   signal(SIGQUIT, sighandler);
43 66
   signal(SIGTERM, sighandler);
44 67
 
45
-  std::cout << "permissionerd (" << configFileName << ") starting"
46
-            << std::endl;
68
+  std::cout << "permissionerd (" << configFileName << ") starting" << std::endl;
47 69
 
48 70
   // set nicecess of process
49 71
   config.getNice().apply();
50 72
 
51 73
   // continuously set ownership and permissions
52 74
   int ret = EXIT_SUCCESS;
53
-  while (go_on) {
75
+  while (daemonCallback.go_on) {
54 76
 
55
-    // set owneship and permissions, measure time it takes
77
+    // set ownership and permissions, measure time it takes
56 78
     std::cout << "permissionerd (" << configFileName
57 79
               << "): setting ownership and permissions" << std::endl;
58 80
     auto begin = std::chrono::steady_clock::now();
59 81
     try {
60
-      config.setPermissions();
82
+      if (! config.setPermissions(daemonCallback)) {
83
+        break;
84
+      }
61 85
     } catch (std::exception const &e) {
62 86
       std::cerr << "error: " << e.what() << std::endl;
63 87
       ret = EXIT_FAILURE;
... ...
@@ -69,11 +93,13 @@ int main(int argc, char const **argv) {
69 93
               << duration.count() << " s" << std::endl;
70 94
 
71 95
     // sleep 10 times as long as the work took plus one second
72
-    auto sleep_time = 10 * duration
73
-                    + std::chrono::duration<int, std::ratio<1>>(1);
74
-    std::this_thread::sleep_for(sleep_time);
96
+    auto sleep_time =
97
+        10 * duration + std::chrono::duration<int, std::ratio<1>>(1);
98
+    if (! iterativeSleep(sleep_time, daemonCallback)) {
99
+      break;
100
+    }
75 101
 
76
-  } // while (go_on)
102
+  } // while (daemonCallback.go_on)
77 103
 
78 104
   std::cout << "permissionerd (" << configFileName << ") shutting down"
79 105
             << std::endl;
... ...
@@ -4,6 +4,7 @@
4 4
  * Copyleft: GNU GENERAL PUBLIC LICENSE version 3 (see LICENSE)
5 5
  */
6 6
 
7
+#include <permissioner/Callback.h>
7 8
 #include <permissioner/Config.h>
8 9
 
9 10
 #include <boost/filesystem.hpp>
... ...
@@ -109,7 +110,8 @@ int main(int argc, char const **argv) {
109 110
   (void)argc;
110 111
   Config config;
111 112
   config.parseFile(argv[1]);
112
-  config.setPermissions();
113
+  Callback callback;
114
+  config.setPermissions(callback);
113 115
 
114 116
   int ret = EXIT_SUCCESS;
115 117
 
116 118