passenger 5.0.7 → 5.0.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (50) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/CHANGELOG +17 -0
  5. data/bin/passenger-install-apache2-module +37 -5
  6. data/build/basics.rb +4 -1
  7. data/build/integration_tests.rb +6 -3
  8. data/build/packaging.rb +64 -4
  9. data/dev/ci/run_jenkins.sh +1 -7
  10. data/dev/ci/run_travis.sh +2 -34
  11. data/doc/Users guide Apache.html +184 -96
  12. data/doc/Users guide Apache.idmap.txt +9 -3
  13. data/doc/Users guide Apache.txt +11 -31
  14. data/doc/Users guide Nginx.html +192 -53
  15. data/doc/Users guide Nginx.idmap.txt +9 -3
  16. data/doc/Users guide Nginx.txt +7 -2
  17. data/doc/Users guide Standalone.html +113 -55
  18. data/doc/Users guide Standalone.idmap.txt +5 -1
  19. data/doc/users_guide_snippets/installation.txt +130 -66
  20. data/doc/users_guide_snippets/tips.txt +38 -0
  21. data/ext/apache2/Hooks.cpp +28 -2
  22. data/ext/common/AgentsStarter.h +6 -0
  23. data/ext/common/ApplicationPool2/AppTypes.h +1 -1
  24. data/ext/common/ApplicationPool2/Group.h +25 -3
  25. data/ext/common/ApplicationPool2/Options.h +1 -1
  26. data/ext/common/ApplicationPool2/Pool/GarbageCollection.h +6 -3
  27. data/ext/common/Constants.h +3 -1
  28. data/ext/common/ServerKit/http_parser.cpp +7 -1
  29. data/ext/common/agents/HelperAgent/Main.cpp +53 -0
  30. data/ext/common/agents/HelperAgent/RequestHandler.h +4 -0
  31. data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +6 -0
  32. data/ext/nginx/ngx_http_passenger_module.c +2 -2
  33. data/lib/phusion_passenger.rb +15 -2
  34. data/lib/phusion_passenger/admin_tools/instance_registry.rb +40 -27
  35. data/lib/phusion_passenger/config/install_agent_command.rb +4 -0
  36. data/lib/phusion_passenger/config/install_standalone_runtime_command.rb +6 -0
  37. data/lib/phusion_passenger/config/installation_utils.rb +8 -2
  38. data/lib/phusion_passenger/config/nginx_engine_compiler.rb +16 -7
  39. data/lib/phusion_passenger/config/validate_install_command.rb +87 -11
  40. data/lib/phusion_passenger/constants.rb +2 -0
  41. data/lib/phusion_passenger/platform_info/apache.rb +114 -33
  42. data/lib/phusion_passenger/platform_info/apache_detector.rb +28 -4
  43. data/lib/phusion_passenger/platform_info/compiler.rb +22 -27
  44. data/lib/phusion_passenger/standalone/start_command.rb +16 -3
  45. data/lib/phusion_passenger/standalone/start_command/builtin_engine.rb +1 -0
  46. data/resources/templates/apache2/rpm_installation_recommended.txt.erb +19 -0
  47. data/resources/templates/standalone/config.erb +3 -2
  48. metadata +3 -3
  49. metadata.gz.asc +7 -7
  50. data/dev/ci/run_rpm_tests.sh +0 -80
@@ -104,6 +104,44 @@ The most likely reason why your application is started as 'nobody' is probably b
104
104
 
105
105
  Whatever user your application runs as, it must have read access to the <<application_root,application root>>, and read/write access to the application's 'logs' directory.
106
106
 
107
+ [[user_switching_rpm_caveats]]
108
+ ==== Red Hat and CentOS caveats
109
+
110
+ NOTE: This information only applies if you installed Passenger through the RPM packages provided by Phusion. If you did not installed Passenger through the RPM packages provided by Phusion, then you can ignore this section.
111
+
112
+ If you installed Passenger through Phusion's RPM packages, and you want to disable user switching, then you must also change <<PassengerInstanceRegistryDir,the location of the instance registry directory>>.
113
+
114
+ This is because our RPMs configure the default instance registry directory to `/var/run/passenger-instreg`, which is only writable by root. If you disable user switching, then the Passenger processes will run as
115
+ ifdef::apache[]
116
+ <<PassengerDefaultUser,PassengerDefaultUser>>,
117
+ endif::[]
118
+ ifdef::nginx[]
119
+ <<PassengerDefaultUser,passenger_default_user>>,
120
+ endif::[]
121
+ which (as long as it's not root) won't be able to write to that directory.
122
+
123
+ Note that any alternative instance registry directory must have the proper SELinux context, allowing the web server to read and write to it. We recommend that you create a directory `/var/lib/passenger-instreg` and give it the label `var_run_t`:
124
+
125
+ ------------------------------------------
126
+ sudo mkdir /var/lib/passenger-instreg
127
+ sudo chcon -t var_run_t /var/lib/passenger-instreg
128
+ ------------------------------------------
129
+
130
+ ifdef::apache[]
131
+ Then, in your Apache config file:
132
+
133
+ ------------------------------------------
134
+ PassengerInstanceRegistryDir /var/lib/passenger-instreg
135
+ ------------------------------------------
136
+ endif::[]
137
+ ifdef::nginx[]
138
+ Then, in your Nginx config file:
139
+
140
+ ------------------------------------------
141
+ passenger_instance_registry_dir /var/lib/passenger-instreg;
142
+ ------------------------------------------
143
+ endif::[]
144
+
107
145
  [[finding_out_app_user]]
108
146
  ==== Finding out what user an application is running as
109
147
 
@@ -117,6 +117,20 @@ private:
117
117
  private:
118
118
  FileSystemException e;
119
119
 
120
+ #ifdef __linux__
121
+ bool selinuxIsEnforcing() const {
122
+ FILE *f = fopen("/sys/fs/selinux/enforce", "r");
123
+ if (f != NULL) {
124
+ char buf;
125
+ size_t ret = fread(&buf, 1, 1, f);
126
+ fclose(f);
127
+ return ret == 1 && buf == '1';
128
+ } else {
129
+ return false;
130
+ }
131
+ }
132
+ #endif
133
+
120
134
  public:
121
135
  ReportFileSystemError(const FileSystemException &ex): e(ex) { }
122
136
 
@@ -124,16 +138,28 @@ private:
124
138
  r->status = 500;
125
139
  ap_set_content_type(r, "text/html; charset=UTF-8");
126
140
  ap_rputs("<h1>Passenger error #2</h1>\n", r);
127
- ap_rputs("An error occurred while trying to access '", r);
141
+ ap_rputs("<p>An error occurred while trying to access '", r);
128
142
  ap_rputs(ap_escape_html(r->pool, e.filename().c_str()), r);
129
143
  ap_rputs("': ", r);
130
144
  ap_rputs(ap_escape_html(r->pool, e.what()), r);
145
+ ap_rputs("</p>\n", r);
146
+
131
147
  if (e.code() == EACCES || e.code() == EPERM) {
132
148
  ap_rputs("<p>", r);
133
149
  ap_rputs("Apache doesn't have read permissions to that file. ", r);
134
150
  ap_rputs("Please fix the relevant file permissions.", r);
135
- ap_rputs("</p>", r);
151
+ ap_rputs("</p>\n", r);
152
+ #ifdef __linux__
153
+ if (selinuxIsEnforcing()) {
154
+ ap_rputs("<p>", r);
155
+ ap_rputs("The permission problems may also be caused by SELinux restrictions. ", r);
156
+ ap_rputs("Please read " APACHE2_DOC_URL "#apache_selinux_permissions to learn ", r);
157
+ ap_rputs("how to fix SELinux permission issues. ", r);
158
+ ap_rputs("</p>", r);
159
+ }
160
+ #endif
136
161
  }
162
+
137
163
  P_ERROR("A filesystem exception occured.\n" <<
138
164
  " Message: " << e.what() << "\n" <<
139
165
  " Backtrace:\n" << e.backtrace());
@@ -348,6 +348,12 @@ public:
348
348
  .setInt ("log_level", getLogLevel());
349
349
  extraParams.addTo(params);
350
350
 
351
+ if (!params.getBool("user_switching", false, true)
352
+ && !params.has("user"))
353
+ {
354
+ params.set("user", params.get("default_user", false, PASSENGER_DEFAULT_USER));
355
+ }
356
+
351
357
  fds = createUnixSocketPair(__FILE__, __LINE__);
352
358
  pid = syscalls::fork();
353
359
  if (pid == 0) {
@@ -115,7 +115,7 @@ private:
115
115
  TRACE_POINT();
116
116
  throw RuntimeException("Not enough buffer space");
117
117
  }
118
- return getFileType(StaticString(buf, pos - buf), cstat, cstatMutex, throttleRate) != FT_NONEXISTANT;
118
+ return getFileType(StaticString(buf, pos - buf - 1), cstat, cstatMutex, throttleRate) != FT_NONEXISTANT;
119
119
  }
120
120
 
121
121
  public:
@@ -389,7 +389,7 @@ public:
389
389
  RouteResult route(const Options &options) const {
390
390
  if (OXT_LIKELY(enabledCount > 0)) {
391
391
  if (options.stickySessionId == 0) {
392
- Process *process = findProcessWithLowestBusyness(enabledProcesses);
392
+ Process *process = findEnabledProcessWithLowestBusyness();
393
393
  if (process->canBeRoutedTo()) {
394
394
  return RouteResult(process);
395
395
  } else {
@@ -496,6 +496,29 @@ public:
496
496
  return NULL;
497
497
  }
498
498
 
499
+ int lowestBusyness = -1;
500
+ Process *leastBusyProcess = NULL;
501
+ ProcessList::const_iterator it;
502
+ ProcessList::const_iterator end = processes.end();
503
+ for (it = processes.begin(); it != end; it++) {
504
+ Process *process = (*it).get();
505
+ int busyness = process->busyness();
506
+ if (lowestBusyness == -1 || lowestBusyness > busyness) {
507
+ lowestBusyness = busyness;
508
+ leastBusyProcess = process;
509
+ }
510
+ }
511
+ return leastBusyProcess;
512
+ }
513
+
514
+ /**
515
+ * Cache-optimized version of findProcessWithLowestBusyness() for the common case.
516
+ */
517
+ Process *findEnabledProcessWithLowestBusyness() const {
518
+ if (enabledProcesses.empty()) {
519
+ return NULL;
520
+ }
521
+
499
522
  int leastBusyProcessIndex = -1;
500
523
  int lowestBusyness = 0;
501
524
  unsigned int i, size = enabledProcessBusynessLevels.size();
@@ -1006,8 +1029,7 @@ public:
1006
1029
  assert(m_spawning || restarting() || poolAtFullCapacity());
1007
1030
 
1008
1031
  if (disablingCount > 0 && !restarting()) {
1009
- Process *process = findProcessWithLowestBusyness(
1010
- disablingProcesses);
1032
+ Process *process = findProcessWithLowestBusyness(disablingProcesses);
1011
1033
  assert(process != NULL);
1012
1034
  if (!process->isTotallyBusy()) {
1013
1035
  return newSession(process, newOptions.currentTime);
@@ -459,7 +459,7 @@ public:
459
459
  environment(DEFAULT_APP_ENV, sizeof(DEFAULT_APP_ENV) - 1),
460
460
  baseURI("/", 1),
461
461
  spawnMethod(DEFAULT_SPAWN_METHOD, sizeof(DEFAULT_SPAWN_METHOD) - 1),
462
- defaultUser("nobody", sizeof("nobody") - 1),
462
+ defaultUser(PASSENGER_DEFAULT_USER, sizeof(PASSENGER_DEFAULT_USER) - 1),
463
463
  ruby(DEFAULT_RUBY, sizeof(DEFAULT_RUBY) - 1),
464
464
  python(DEFAULT_PYTHON, sizeof(DEFAULT_PYTHON) - 1),
465
465
  nodejs(DEFAULT_NODEJS, sizeof(DEFAULT_NODEJS) - 1),
@@ -71,8 +71,7 @@ void checkWhetherProcessCanBeGarbageCollected(GarbageCollectorState &state,
71
71
  assert(maxIdleTime > 0);
72
72
  unsigned long long processGcTime = process->lastUsed + maxIdleTime;
73
73
  if (process->sessions == 0
74
- && state.now >= processGcTime
75
- && (unsigned long) group->getProcessCount() > group->options.minProcesses)
74
+ && state.now >= processGcTime)
76
75
  {
77
76
  if (output.capacity() == 0) {
78
77
  output.reserve(group->enabledCount);
@@ -96,12 +95,16 @@ void garbageCollectProcessesInGroup(GarbageCollectorState &state,
96
95
  processesToGc);
97
96
  }
98
97
 
98
+ p_it = processesToGc.begin();
99
99
  p_end = processesToGc.end();
100
- for (p_it = processesToGc.begin(); p_it != p_end; p_it++) {
100
+ while (p_it != p_end
101
+ && (unsigned long) group->getProcessCount() > group->options.minProcesses)
102
+ {
101
103
  ProcessPtr process = *p_it;
102
104
  P_DEBUG("Garbage collect idle process: " << process->inspect() <<
103
105
  ", group=" << group->name);
104
106
  group->detach(process, state.actions);
107
+ p_it++;
105
108
  }
106
109
  }
107
110
 
@@ -114,7 +114,9 @@
114
114
 
115
115
  #define NGINX_DOC_URL "https://www.phusionpassenger.com/documentation/Users%20guide%20Nginx.html"
116
116
 
117
- #define PASSENGER_VERSION "5.0.7"
117
+ #define PASSENGER_DEFAULT_USER "nobody"
118
+
119
+ #define PASSENGER_VERSION "5.0.8"
118
120
 
119
121
  #define POOL_HELPER_THREAD_STACK_SIZE 262144
120
122
 
@@ -2062,6 +2062,7 @@ http_parse_host_char(enum http_host_state s, const char ch) {
2062
2062
 
2063
2063
  static int
2064
2064
  http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
2065
+ assert(u->field_set & (1 << UF_HOST));
2065
2066
  enum http_host_state s;
2066
2067
 
2067
2068
  const char *p;
@@ -2206,7 +2207,12 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
2206
2207
 
2207
2208
  /* host must be present if there is a schema */
2208
2209
  /* parsing http:///toto will fail */
2209
- if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
2210
+ if ((u->field_set & (1 << UF_SCHEMA)) &&
2211
+ (u->field_set & (1 << UF_HOST)) == 0) {
2212
+ return 1;
2213
+ }
2214
+
2215
+ if (u->field_set & (1 << UF_HOST)) {
2210
2216
  if (http_parse_host(buf, u, found_at) != 0) {
2211
2217
  return 1;
2212
2218
  }
@@ -31,6 +31,9 @@
31
31
  #include <sched.h>
32
32
  #include <pthread.h>
33
33
  #endif
34
+ #ifdef USE_SELINUX
35
+ #include <selinux/selinux.h>
36
+ #endif
34
37
 
35
38
  #include <sys/types.h>
36
39
  #include <sys/socket.h>
@@ -268,6 +271,47 @@ makeFileWorldReadableAndWritable(const string &path) {
268
271
  } while (ret == -1 && errno == EINTR);
269
272
  }
270
273
 
274
+ #ifdef USE_SELINUX
275
+ // Set next socket context to *:system_r:passenger_instance_httpd_socket_t
276
+ static void
277
+ setSelinuxSocketContext() {
278
+ security_context_t currentCon;
279
+ string newCon;
280
+ int e;
281
+
282
+ if (getcon(&currentCon) == -1) {
283
+ e = errno;
284
+ P_DEBUG("Unable to obtain SELinux context: " <<
285
+ strerror(e) << " (errno=" << e << ")");
286
+ return;
287
+ }
288
+
289
+ P_DEBUG("Current SELinux process context: " << currentCon);
290
+
291
+ if (strstr(currentCon, ":unconfined_r:unconfined_t:") == NULL) {
292
+ goto cleanup;
293
+ }
294
+
295
+ newCon = replaceString(currentCon,
296
+ ":unconfined_r:unconfined_t:",
297
+ ":object_r:passenger_instance_httpd_socket_t:");
298
+ if (setsockcreatecon((security_context_t) newCon.c_str()) == -1) {
299
+ e = errno;
300
+ P_WARN("Cannot set SELinux socket context to " << newCon <<
301
+ ": " << strerror(e) << " (errno=" << e << ")");
302
+ goto cleanup;
303
+ }
304
+
305
+ cleanup:
306
+ freecon(currentCon);
307
+ }
308
+
309
+ static void
310
+ resetSelinuxSocketContext() {
311
+ setsockcreatecon(NULL);
312
+ }
313
+ #endif
314
+
271
315
  static void
272
316
  startListening() {
273
317
  TRACE_POINT();
@@ -275,9 +319,18 @@ startListening() {
275
319
  vector<string> addresses = agentsOptions->getStrSet("server_addresses");
276
320
  vector<string> adminAddresses = agentsOptions->getStrSet("server_admin_addresses", false);
277
321
 
322
+ #ifdef USE_SELINUX
323
+ // Set SELinux context on the first socket that we create
324
+ // so that the web server can access it.
325
+ setSelinuxSocketContext();
326
+ #endif
327
+
278
328
  for (unsigned int i = 0; i < addresses.size(); i++) {
279
329
  wo->serverFds[i] = createServer(addresses[i], 0, true,
280
330
  __FILE__, __LINE__);
331
+ #ifdef USE_SELINUX
332
+ resetSelinuxSocketContext();
333
+ #endif
281
334
  P_LOG_FILE_DESCRIPTOR_PURPOSE(wo->serverFds[i],
282
335
  "Server address: " << addresses[i]);
283
336
  if (getSocketAddressType(addresses[i]) == SAT_UNIX) {
@@ -150,6 +150,8 @@ private:
150
150
  HashedStaticString HTTP_CONNECTION;
151
151
  HashedStaticString HTTP_STATUS;
152
152
  HashedStaticString HTTP_TRANSFER_ENCODING;
153
+ HashedStaticString HTTP_X_SENDFILE;
154
+ HashedStaticString HTTP_X_ACCEL_REDIRECT;
153
155
 
154
156
  unsigned int threadNumber;
155
157
  StaticString serverLogName;
@@ -215,6 +217,8 @@ public:
215
217
  HTTP_CONNECTION("connection"),
216
218
  HTTP_STATUS("status"),
217
219
  HTTP_TRANSFER_ENCODING("transfer-encoding"),
220
+ HTTP_X_SENDFILE("x-sendfile"),
221
+ HTTP_X_ACCEL_REDIRECT("x-accel-redirect"),
218
222
 
219
223
  threadNumber(_threadNumber),
220
224
  turboCaching(getTurboCachingInitialState(_agentsOptions))
@@ -335,6 +335,12 @@ onAppResponseBegin(Client *client, Request *req) {
335
335
  req->wantKeepAlive = false;
336
336
  }
337
337
  }
338
+ if (resp->headers.lookup(HTTP_X_SENDFILE) != NULL
339
+ || resp->headers.lookup(HTTP_X_ACCEL_REDIRECT) != NULL)
340
+ {
341
+ // https://github.com/phusion/passenger/issues/1498
342
+ resp->wantKeepAlive = false;
343
+ }
338
344
 
339
345
  prepareAppResponseCaching(client, req);
340
346
 
@@ -433,7 +433,7 @@ pre_config_init(ngx_conf_t *cf)
433
433
  */
434
434
  static ngx_int_t
435
435
  init_module(ngx_cycle_t *cycle) {
436
- if (passenger_main_conf.root_dir.len != 0) {
436
+ if (passenger_main_conf.root_dir.len != 0 && !ngx_test_config) {
437
437
  if (first_start) {
438
438
  /* Ignore SIGPIPE now so that, if the helper server fails to start,
439
439
  * Nginx doesn't get killed by the default SIGPIPE handler upon
@@ -463,7 +463,7 @@ static ngx_int_t
463
463
  init_worker_process(ngx_cycle_t *cycle) {
464
464
  ngx_core_conf_t *core_conf;
465
465
 
466
- if (passenger_main_conf.root_dir.len != 0) {
466
+ if (passenger_main_conf.root_dir.len != 0 && !ngx_test_config) {
467
467
  save_master_process_pid(cycle);
468
468
 
469
469
  core_conf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
@@ -30,7 +30,7 @@ module PhusionPassenger
30
30
 
31
31
  PACKAGE_NAME = 'passenger'
32
32
  # Run 'rake ext/common/Constants.h' after changing this number.
33
- VERSION_STRING = '5.0.7'
33
+ VERSION_STRING = '5.0.8'
34
34
 
35
35
  PREFERRED_NGINX_VERSION = '1.6.3'
36
36
  NGINX_SHA256_CHECKSUM = '0a98e95b366e4d6042f331e1fa4d70e18fd1e49d8993e589008e70e742b7e757'
@@ -149,7 +149,20 @@ module PhusionPassenger
149
149
  # method was used. Can be 'deb', 'rpm', 'homebrew', 'test'
150
150
  # or 'unknown'.
151
151
  def self.packaging_method
152
- return @custom_packaging_method
152
+ return @packaging_method
153
+ end
154
+
155
+ def self.packaging_method_description
156
+ case packaging_method
157
+ when "deb"
158
+ "Debian packages"
159
+ when "rpm"
160
+ "RPM packages"
161
+ when "homebrew"
162
+ "Homebrew"
163
+ else
164
+ "gem or tarball"
165
+ end
153
166
  end
154
167
 
155
168
  # Whether the current Phusion Passenger installation is installed
@@ -29,38 +29,42 @@ module PhusionPassenger
29
29
  module AdminTools
30
30
 
31
31
  class InstanceRegistry
32
- def initialize(path = nil)
33
- @path = path || default_path
32
+ def initialize(paths = nil)
33
+ @paths = [paths || default_paths].flatten
34
34
  end
35
35
 
36
36
  def list(options = {})
37
37
  options = {
38
38
  :clean_stale_or_corrupted => true
39
39
  }.merge(options)
40
+
40
41
  instances = []
41
42
 
42
- Dir["#{@path}/passenger.*"].each do |dir|
43
- instance = Instance.new(dir)
44
- case instance.state
45
- when :good
46
- if instance.locked?
47
- instances << instance
48
- elsif options[:clean_stale_or_corrupted]
49
- cleanup(dir)
50
- end
51
- when :structure_version_unsupported
52
- next
53
- when :corrupted
54
- if !instance.locked? && options[:clean_stale_or_corrupted]
55
- cleanup(dir)
56
- end
57
- when :not_finalized
58
- if instance.stale? && options[:clean_stale_or_corrupted]
59
- cleanup(dir)
43
+ @paths.each do |path|
44
+ Dir["#{path}/passenger.*"].each do |dir|
45
+ instance = Instance.new(dir)
46
+ case instance.state
47
+ when :good
48
+ if instance.locked?
49
+ instances << instance
50
+ elsif options[:clean_stale_or_corrupted]
51
+ cleanup(dir)
52
+ end
53
+ when :structure_version_unsupported
54
+ next
55
+ when :corrupted
56
+ if !instance.locked? && options[:clean_stale_or_corrupted]
57
+ cleanup(dir)
58
+ end
59
+ when :not_finalized
60
+ if instance.stale? && options[:clean_stale_or_corrupted]
61
+ cleanup(dir)
62
+ end
60
63
  end
61
64
  end
62
65
  end
63
- return instances
66
+
67
+ instances
64
68
  end
65
69
 
66
70
  def find_by_name(name, options = {})
@@ -78,13 +82,22 @@ module PhusionPassenger
78
82
  end
79
83
 
80
84
  private
81
- def default_path
82
- ["PASSENGER_INSTANCE_REGISTRY_DIR", "TMPDIR"].each do |name|
83
- if ENV.has_key?(name) && !ENV[name].empty?
84
- return ENV[name]
85
- end
85
+ def default_paths
86
+ if result = string_env("PASSENGER_INSTANCE_REGISTRY_DIR")
87
+ return result
88
+ end
89
+
90
+ # The RPM packages configure Apache and Nginx to use /var/run/passenger-instreg
91
+ # as the instance registry dir. See https://github.com/phusion/passenger/issues/1475
92
+ [string_env("TMPDIR") || "/tmp", "/var/run/passenger-instreg"]
93
+ end
94
+
95
+ def string_env(name)
96
+ if (result = ENV[name]) && !result.empty?
97
+ result
98
+ else
99
+ nil
86
100
  end
87
- return "/tmp"
88
101
  end
89
102
 
90
103
  def cleanup(path)