public
Description: Phusion Passenger (mod_rails)
Homepage: http://www.modrails.com/
Clone URL: git://github.com/FooBarWidget/passenger.git
Search Repo:
Click here to lend your support to: passenger and make a donation at www.pledgie.com !
- Improve API documentation.
- SpawnManager (C++):
   * Improve error handling.
   * Will now attempt to restart the spawn server if it died.
   * Is now thread-safe.
   * Allows specifying a group when spawning apps.
- Ruby spawn server:
   * Allows specifying a group when spawning apps. This doesn't do 
   anything yet.
FooBarWidget (author)
Fri Feb 01 12:38:11 -0800 2008
commit  a92649cae9d3a26508f13b8f2cb73694216335ac
tree    5a1f2a4c17df1cb205f4f5eedb2a1e71e245c010
parent  42b311bb9b1137922bb9d873d58cc6e7672886c2
...
 
 
 
 
 
1
2
3
...
1
2
3
4
5
6
7
8
0
@@ -1,3 +1,8 @@
0
+/**
0
+ * This file implements an APR bucket which understands the request handler's
0
+ * (= the dispatcher's) protocol.
0
+ * See http://www.apachetutor.org/dev/brigades for information on APR buckets.
0
+ */
0
 #ifndef _DISPATCHER_BUCKET_H_
0
 #define _DISPATCHER_BUCKET_H_
0
 
...
1
2
3
4
5
6
7
 
8
9
10
...
16
17
18
19
20
21
22
23
24
25
26
 
 
 
27
28
29
...
1
2
3
 
4
5
 
6
7
8
9
...
15
16
17
 
 
 
 
 
 
 
 
18
19
20
21
22
23
0
@@ -1,10 +1,9 @@
0
 #ifndef _PASSENGER_EXCEPTIONS_H_
0
 #define _PASSENGER_EXCEPTIONS_H_
0
 
0
-#include <apr_strings.h>
0
 #include <exception>
0
 #include <string>
0
-#include <cstring>
0
+#include <sstream>
0
 
0
 namespace Passenger {
0
 
0
@@ -16,14 +15,9 @@ private:
0
   int m_code;
0
 public:
0
   SystemException(const string &message, int errorCode) {
0
-    char buffer[10];
0
-    
0
-    msg.assign(message);
0
-    msg.append(": ");
0
-    msg.append(strerror(errorCode));
0
-    if (apr_snprintf(buffer, sizeof(buffer), " (%d)", errorCode) > 0) {
0
-      msg.append(buffer);
0
-    }
0
+    stringstream str(message);
0
+    str << ":" << strerror(errorCode) << " (" << errorCode << ")";
0
+    msg = str.str();
0
     m_code = errorCode;
0
   }
0
   
...
279
280
281
282
 
283
284
285
...
279
280
281
 
282
283
284
285
0
@@ -279,7 +279,7 @@ public:
0
       
0
       return OK;
0
     } catch (const exception &e) {
0
-      ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "mod_passenger unknown error: %s", e.what());
0
+      ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "mod_passenger: unknown uncaught error: %s", e.what());
0
       return HTTP_INTERNAL_SERVER_ERROR;
0
     }
0
   }
...
8
9
10
11
 
12
13
14
 
15
16
17
...
8
9
10
 
11
12
13
 
14
15
16
17
0
@@ -8,10 +8,10 @@ LIBTOOL_OBJECTS=Configuration.o,Hooks.o,DispatcherBucket.o,Utils.o
0
 .PHONY: all install clean start restart stop
0
 
0
 all: $(OBJECTS)
0
-  $(APXS) -c mod_rails.c -Wc,$(LIBTOOL_OBJECTS) -Wl,-lstdc++
0
+  $(APXS) -c mod_rails.c -Wc,$(LIBTOOL_OBJECTS) -Wl,-lstdc++,-lboost_thread
0
 
0
 install: $(OBJECTS)
0
-  $(APXS) -i -c mod_rails.c -Wc,$(LIBTOOL_OBJECTS) -Wl,-lstdc++
0
+  $(APXS) -i -c mod_rails.c -Wc,$(LIBTOOL_OBJECTS) -Wl,-lstdc++,-lboost_thread
0
 
0
 Configuration.o: Configuration.cpp
0
   $(CXX) $(CXXFLAGS) -c Configuration.cpp
...
4
5
6
 
7
8
9
...
16
17
18
 
19
20
21
...
26
27
28
 
 
29
30
31
32
33
34
 
 
 
35
36
37
...
47
48
49
 
 
 
 
 
 
 
50
51
 
52
53
54
55
 
56
57
58
59
60
61
62
63
64
65
66
67
68
 
69
70
71
72
73
74
 
 
 
 
 
 
 
 
75
76
77
 
78
79
80
...
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
113
114
115
116
117
118
119
120
121
122
 
 
 
 
123
124
125
126
127
128
 
129
130
131
132
133
134
135
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
139
140
141
 
 
 
 
142
143
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
147
148
149
 
 
 
 
 
 
 
 
 
 
 
 
 
150
151
152
153
154
155
156
157
...
4
5
6
7
8
9
10
...
17
18
19
20
21
22
23
...
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
...
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 
68
 
69
70
 
 
 
 
 
 
 
 
 
 
 
 
71
72
73
 
 
 
 
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
98
99
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
 
 
 
 
 
 
 
 
 
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
 
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
 
197
198
199
200
201
202
 
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
 
 
 
241
242
243
244
245
246
247
248
249
250
251
252
253
254
 
 
 
 
255
256
257
0
@@ -4,6 +4,7 @@
0
 #include <string>
0
 #include <list>
0
 #include <boost/shared_ptr.hpp>
0
+#include <boost/thread/mutex.hpp>
0
 
0
 #include <sys/types.h>
0
 #include <sys/wait.h>
0
@@ -16,6 +17,7 @@
0
 #include "Application.h"
0
 #include "MessageChannel.h"
0
 #include "Exceptions.h"
0
+#include "Utils.h"
0
 
0
 namespace Passenger {
0
 
0
@@ -26,12 +28,17 @@ using namespace boost;
0
  * This class is responsible for spawning new instances of Ruby on Rails applications.
0
  * Use the spawn() method to do so.
0
  *
0
+ * This class is fully thread-safe.
0
+ *
0
  * <h2>Implementation details</h2>
0
  * Internally, it makes use of a spawn server, which is written in Ruby. This server
0
  * is automatically started when a SpawnManager instance is created, and automatically
0
  * shutdown when that instance is destroyed. Spawning requests are sent to the server,
0
  * and details about the spawned process is returned.
0
  *
0
+ * If the spawn server dies during the middle of an operation, it will be restarted.
0
+ * See spawn() for full details.
0
+ *
0
  * The communication channel with the server is anonymous, i.e. no other processes
0
  * can access the communication channel, so communication is guaranteed to be safe
0
  * (unless, of course, if the spawn server itself is a trojan).
0
@@ -47,34 +54,35 @@ using namespace boost;
0
  */
0
 class SpawnManager {
0
 private:
0
+  string spawnServerCommand;
0
+  string logFile;
0
+  string environment;
0
+  string rubyCommand;
0
+  
0
+  mutex lock;
0
+  
0
   MessageChannel channel;
0
   pid_t pid;
0
+  bool serverNeedsRestart;
0
 
0
-public:
0
   /**
0
-   * Construct a new SpawnManager.
0
+   * Restarts the spawn server.
0
    *
0
-   * @param spawnServerCommand The filename of the spawn server to use.
0
-   * @param logFile Specify a log file that the spawn manager should use.
0
-   * Messages on its standard output and standard error channels
0
-   * will be written to this log file. If an empty string is
0
-   * specified, no log file will be used, and the spawn server
0
-   * will use the same standard output/error channels as the
0
-   * current process.
0
-   * @param environment The RAILS_ENV environment that all RoR applications
0
-   * should use. If an empty string is specified, the current value
0
-   * of the RAILS_ENV environment variable will be used.
0
-   * @param rubyCommand The Ruby interpreter's command.
0
-   * @param SystemException An error occured while trying to setup the spawn server.
0
+   * @throws SystemException An error occured while trying to setup the spawn server.
0
    * @throws IOException The specified log file could not be opened.
0
    */
0
-  SpawnManager(const string &spawnServerCommand,
0
-   const string &logFile = "",
0
-   const string &environment = "production",
0
-   const string &rubyCommand = "ruby") {
0
+  void restartServer() {
0
+    if (pid != 0) {
0
+      channel.close();
0
+      // TODO: should not wait infinitely
0
+      waitpid(pid, NULL, 0);
0
+      pid = 0;
0
+    }
0
+    
0
     int fds[2];
0
     FILE *logFileHandle = NULL;
0
     
0
+    serverNeedsRestart = true;
0
     if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) {
0
       throw SystemException("Cannot create a Unix socket", errno);
0
     }
0
@@ -90,68 +98,160 @@ public:
0
 
0
     pid = fork();
0
     if (pid == 0) {
0
-      pid = fork();
0
-      if (pid == 0) {
0
-        if (!logFile.empty()) {
0
-          dup2(fileno(logFileHandle), STDERR_FILENO);
0
-          fclose(logFileHandle);
0
-        }
0
-        dup2(STDERR_FILENO, STDOUT_FILENO);
0
-        if (!environment.empty()) {
0
-          setenv("RAILS_ENV", environment.c_str(), true);
0
-        }
0
-        dup2(fds[1], STDIN_FILENO);
0
-        close(fds[0]);
0
-        close(fds[1]);
0
-        
0
-        // Close all other file descriptors
0
-        for (int i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
0
-          if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) {
0
-            close(i);
0
-          }
0
+      if (!logFile.empty()) {
0
+        dup2(fileno(logFileHandle), STDERR_FILENO);
0
+        fclose(logFileHandle);
0
+      }
0
+      dup2(STDERR_FILENO, STDOUT_FILENO);
0
+      if (!environment.empty()) {
0
+        setenv("RAILS_ENV", environment.c_str(), true);
0
+      }
0
+      dup2(fds[1], STDIN_FILENO);
0
+      close(fds[0]);
0
+      close(fds[1]);
0
+      
0
+      // Close all other file descriptors
0
+      for (int i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
0
+        if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) {
0
+          close(i);
0
         }
0
-        
0
-        execlp(rubyCommand.c_str(), rubyCommand.c_str(), spawnServerCommand.c_str(), NULL);
0
-        fprintf(stderr, "Unable to run %s: %s\n", rubyCommand.c_str(), strerror(errno));
0
-        _exit(1);
0
-      } else if (pid == -1) {
0
-        fprintf(stderr, "Unable to fork a process: %s\n", strerror(errno));
0
-        _exit(0);
0
-      } else {
0
-        _exit(0);
0
       }
0
+      
0
+      execlp(rubyCommand.c_str(), rubyCommand.c_str(), spawnServerCommand.c_str(), NULL);
0
+      fprintf(stderr, "Unable to run %s: %s\n", rubyCommand.c_str(), strerror(errno));
0
+      _exit(1);
0
     } else if (pid == -1) {
0
       close(fds[0]);
0
       close(fds[1]);
0
       if (logFileHandle != NULL) {
0
         fclose(logFileHandle);
0
       }
0
+      pid = 0;
0
       throw SystemException("Unable to fork a process", errno);
0
     } else {
0
       close(fds[1]);
0
       if (!logFile.empty()) {
0
         fclose(logFileHandle);
0
       }
0
-      waitpid(pid, NULL, 0);
0
       channel = MessageChannel(fds[0]);
0
+      serverNeedsRestart = false;
0
+    }
0
+  }
0
+
0
+public:
0
+  /**
0
+   * Thrown when SpawnManager tried to restart the spawn server, but failed.
0
+   * Use getSubException() to find out more details about the failure.
0
+   */
0
+  class RestartException: public exception {
0
+  private:
0
+    shared_ptr<exception> m_subexception;
0
+  public:
0
+    RestartException(shared_ptr<exception> subexception)
0
+      : m_subexception(subexception) {}
0
+    
0
+    virtual ~RestartException() throw() {}
0
+    
0
+    virtual const char *what() const throw() {
0
+      return m_subexception->what();
0
+    }
0
+  
0
+    /**
0
+     * An exception which contains more details about why the restart failed.
0
+     */
0
+    shared_ptr<exception> getSubException() const throw() {
0
+      return m_subexception;
0
     }
0
+  };
0
+
0
+  /**
0
+   * Construct a new SpawnManager.
0
+   *
0
+   * @param spawnServerCommand The filename of the spawn server to use.
0
+   * @param logFile Specify a log file that the spawn manager should use.
0
+   * Messages on its standard output and standard error channels
0
+   * will be written to this log file. If an empty string is
0
+   * specified, no log file will be used, and the spawn server
0
+   * will use the same standard output/error channels as the
0
+   * current process.
0
+   * @param environment The RAILS_ENV environment that all RoR applications
0
+   * should use. If an empty string is specified, the current value
0
+   * of the RAILS_ENV environment variable will be used.
0
+   * @param rubyCommand The Ruby interpreter's command.
0
+   * @throws SystemException An error occured while trying to setup the spawn server.
0
+   * @throws IOException The specified log file could not be opened.
0
+   */
0
+  SpawnManager(const string &spawnServerCommand,
0
+   const string &logFile = "",
0
+   const string &environment = "production",
0
+   const string &rubyCommand = "ruby") {
0
+    this->spawnServerCommand = spawnServerCommand;
0
+    this->logFile = logFile;
0
+    this->environment = environment;
0
+    this->rubyCommand = rubyCommand;
0
+    pid = 0;
0
+    restartServer();
0
   }
0
   
0
   ~SpawnManager() throw() {
0
-    channel.close();
0
+    if (pid != 0) {
0
+      channel.close();
0
+      waitpid(pid, NULL, 0);
0
+    }
0
   }
0
   
0
-  ApplicationPtr spawn(const string &appRoot, const string &username = "") {
0
+  /**
0
+   * Spawn a new instance of a Ruby on Rails application.
0
+   *
0
+   * If something went wrong in the server during the spawning process,
0
+   * then an IOException or a SystemException will be thrown. The server will
0
+   * be restarted next time spawn() is called. If the server crashes during
0
+   * the restart, a RestartException will be thrown.
0
+   *
0
+   * @param appRoot The application root of a RoR application, i.e. the folder that
0
+   * contains 'app/', 'public/', 'config/', etc. This must be a valid directory,
0
+   * but the path does not have to be absolute.
0
+   * @param user The user
0
+   * @return A smart pointer to an Application object, which represents the application
0
+   * instance that has been spawned. Use this object to communicate with the
0
+   * spawned application.
0
+   * @throws IOException Something went wrong during spawning.
0
+   * @throws SystemException Something went wrong during spawning.
0
+   * @throws RestartException An attempt to restart the spawn server was made, but that failed.
0
+   */
0
+  ApplicationPtr spawn(const string &appRoot, const string &user = "", const string &group = "") {
0
     vector<string> args;
0
+    mutex::scoped_lock l(lock);
0
+    
0
+    if (serverNeedsRestart) {
0
+      try {
0
+        P_TRACE("Restarting spawn server.");
0
+        restartServer();
0
+      } catch (const IOException &e) {
0
+        P_TRACE("Failed to restart spawn server: " << e.what());
0
+        shared_ptr<IOException> copy(new IOException(e));
0
+        throw RestartException(copy);
0
+      } catch (const SystemException &e) {
0
+        P_TRACE("Failed to restart spawn server: " << e.what());
0
+        shared_ptr<SystemException> copy(new SystemException(e));
0
+        throw RestartException(copy);
0
+      }
0
+    }
0
   
0
-    channel.write("spawn_application", appRoot.c_str(), username.c_str(), NULL);
0
-    if (!channel.read(args)) {
0
-      throw IOException("The spawn manager server has exited unexpectedly.");
0
+    try {
0
+      channel.write("spawn_application", appRoot.c_str(), user.c_str(), group.c_str(), NULL);
0
+      if (!channel.read(args)) {
0
+        throw IOException("The spawn server has exited unexpectedly.");
0
+      }
0
+      pid_t pid = atoi(args.front().c_str());
0
+      int reader = channel.readFileDescriptor();
0
+      int writer = channel.readFileDescriptor();
0
+      return ApplicationPtr(new Application(appRoot, pid, reader, writer));
0
+    } catch (const exception &e) {
0
+      P_TRACE("Spawn server died. Will restart it next time.");
0
+      serverNeedsRestart = true;
0
+      throw;
0
     }
0
-    pid_t pid = atoi(args.front().c_str());
0
-    int reader = channel.readFileDescriptor();
0
-    int writer = channel.readFileDescriptor();
0
-    return ApplicationPtr(new Application(appRoot, pid, reader, writer));
0
   }
0
 };
0
 
...
31
32
33
34
35
 
 
36
37
38
39
 
 
40
41
 
42
43
44
 
 
45
46
47
 
 
48
49
50
...
31
32
33
 
 
34
35
36
37
 
 
38
39
40
 
41
42
43
 
44
45
46
47
 
48
49
50
51
52
0
@@ -31,20 +31,22 @@ class ApplicationSpawner < AbstractServer
0
   # or a directory that doesn't appear to be a Rails application root directory,
0
   # then an ArgumentError will be raised.
0
   #
0
-  # You may optionally specify _username_. If specified, ApplicationSpawner will
0
-  # switch very spawned instance to the given user. This only works if
0
+  # You may optionally specify _user_ and _group_. If specified, ApplicationSpawner will
0
+  # switch very spawned instance to the given user and group. This only works if
0
   # ApplicationSpawner is running as root (or has the capability to change the
0
   # current process's user).
0
-  # If given an invalid username, then an ArgumentError will be raised.
0
-  # If the current process's user cannot be changed, then a UserChangeError
0
+  # If given an invalid user or group, then an ArgumentError will be raised.
0
+  # If the current process's user/group cannot be changed, then a UserChangeError
0
   # will be raised.
0
-  def initialize(app_root, username = nil)
0
+  def initialize(app_root, user = nil, group = nil)
0
     super()
0
     @app_root = normalize_path(app_root)
0
-    @username = username
0
+    @user = user
0
+    @group = group
0
     self.time = Time.now
0
     assert_valid_app_root(@app_root)
0
-    assert_valid_username(@username) unless @username.nil?
0
+    assert_valid_username(@user) unless @user.nil?
0
+    assert_valid_groupname(@group) unless @group.nil?
0
     define_message_handler(:spawn_application, :handle_spawn_application)
0
   end
0
   
...
65
66
67
68
 
69
70
71
 
 
72
73
 
74
75
76
...
152
153
154
155
 
 
 
156
157
158
159
 
160
161
162
...
65
66
67
 
68
69
70
 
71
72
73
 
74
75
76
77
...
153
154
155
 
156
157
158
159
160
161
 
162
163
164
165
0
@@ -65,12 +65,13 @@ class FrameworkSpawner < AbstractServer
0
   # or a problem in the Ruby on Rails framework), then a ApplicationSpawner::SpawnError
0
   # will be raised. The application's exception message will be printed to standard
0
   # error.
0
-  def spawn_application(app_root, username = nil)
0
+  def spawn_application(app_root, user = nil, group = nil)
0
     app_root = normalize_path(app_root)
0
     assert_valid_app_root(app_root)
0
-    assert_valid_username(username) unless username.nil?
0
+    assert_valid_username(user) unless user.nil?
0
+    assert_valid_groupname(group) unless group.nil?
0
     begin
0
-      send_to_server("spawn_application", app_root, username)
0
+      send_to_server("spawn_application", app_root, user, group)
0
       pid = recv_from_server
0
       reader = recv_io_from_server
0
       writer = recv_io_from_server
0
@@ -152,11 +153,13 @@ private
0
     end
0
   end
0
 
0
-  def handle_spawn_application(app_root, username)
0
+  def handle_spawn_application(app_root, user, group)
0
+    user = nil if user && user.empty?
0
+    group = nil if group && group.empty?
0
     @spawners_lock.synchronize do
0
       spawner = @spawners[app_root]
0
       if spawner.nil?
0
-        spawner = ApplicationSpawner.new(app_root, username.empty? ? nil : username)
0
+        spawner = ApplicationSpawner.new(app_root, user, group)
0
         spawner.file_descriptors_to_close = [@child_socket.fileno]
0
         spawner.start
0
         @spawners[app_root] = spawner
...
24
25
26
27
28
29
30
...
76
77
78
 
79
80
81
...
24
25
26
 
27
28
29
...
75
76
77
78
79
80
81
0
@@ -24,7 +24,6 @@ class RequestHandler
0
     
0
     def self.write(io, data)
0
       io.write([data.size].pack('n') << data)
0
-      io.flush
0
     end
0
   end
0
 
0
@@ -76,6 +75,7 @@ class RequestHandler
0
     write_chunk("\r\n")
0
     write_chunk(content)
0
     write_chunk("")
0
+    @writer.flush
0
   end
0
 
0
 private
...
34
35
36
37
 
38
39
40
...
46
47
48
49
 
50
51
52
...
116
117
118
119
120
121
 
 
 
 
122
123
124
...
34
35
36
 
37
38
39
40
...
46
47
48
 
49
50
51
52
...
116
117
118
 
 
 
119
120
121
122
123
124
125
0
@@ -34,7 +34,7 @@ class SpawnManager
0
     end
0
   end
0
 
0
-  def spawn_application(app_root, username = nil)
0
+  def spawn_application(app_root, user = nil, group = nil)
0
     framework_version = Application.get_framework_version(app_root)
0
     spawner = nil
0
     @lock.synchronize do
0
@@ -46,7 +46,7 @@ class SpawnManager
0
       end
0
     end
0
     spawner.time = Time.now
0
-    return spawner.spawn_application(app_root, username)
0
+    return spawner.spawn_application(app_root, user, group)
0
   end
0
   
0
   def server_main(unix_socket)
0
@@ -116,9 +116,10 @@ private
0
     @previous_signal_handlers = {}
0
   end
0
 
0
-  def handle_spawn_application(app_root, username = nil)
0
-    username = nil if username && username.empty?
0
-    app = spawn_application(app_root, username)
0
+  def handle_spawn_application(app_root, user, group)
0
+    user = nil if user && user.empty?
0
+    group = nil if group && group.empty?
0
+    app = spawn_application(app_root, user, group)
0
     @channel.write(app.pid)
0
     @channel.send_io(app.reader)
0
     @channel.send_io(app.writer)
...
26
27
28
29
30
 
 
 
 
 
 
 
31
32
33
...
26
27
28
 
 
29
30
31
32
33
34
35
36
37
38
0
@@ -26,8 +26,13 @@ private
0
   end
0
   
0
   def assert_valid_username(username)
0
-    # If username does not exist then getpwnam() will throw an ArgumentError.
0
-    username.nil? || !Etc.getpwnam(username).nil?
0
+    # If username does not exist then getpwnam() will raise an ArgumentError.
0
+    username && Etc.getpwnam(username)
0
+  end
0
+  
0
+  def assert_valid_groupname(groupname)
0
+    # If groupname does not exist then getgrnam() will raise an ArgumentError.
0
+    groupname && Etc.getgrnam(groupname)
0
   end
0
   
0
   def print_exception(current_location, exception)

Comments

    No one has commented yet.