passenger 2.1.2 → 2.1.3

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 (244) hide show
  1. data/Rakefile +7 -5
  2. data/doc/Architectural overview.html +729 -1
  3. data/doc/Architectural overview.txt +0 -5
  4. data/doc/Security of user switching support.html +605 -1
  5. data/doc/Users guide.html +110 -106
  6. data/doc/Users guide.txt +49 -0
  7. data/doc/cxxapi/ApplicationPoolServer_8h-source.html +400 -372
  8. data/doc/cxxapi/ApplicationPool_8h-source.html +1 -1
  9. data/doc/cxxapi/Application_8h-source.html +1 -1
  10. data/doc/cxxapi/Bucket_8h-source.html +1 -1
  11. data/doc/cxxapi/CachedFileStat_8h-source.html +56 -65
  12. data/doc/cxxapi/Configuration_8h-source.html +40 -32
  13. data/doc/cxxapi/DirectoryMapper_8h-source.html +1 -1
  14. data/doc/cxxapi/DummySpawnManager_8h-source.html +1 -1
  15. data/doc/cxxapi/Exceptions_8h-source.html +1 -1
  16. data/doc/cxxapi/FileChecker_8h-source.html +29 -30
  17. data/doc/cxxapi/Hooks_8h-source.html +1 -1
  18. data/doc/cxxapi/Logging_8h-source.html +1 -1
  19. data/doc/cxxapi/MessageChannel_8h-source.html +1 -1
  20. data/doc/cxxapi/PoolOptions_8h-source.html +1 -1
  21. data/doc/cxxapi/SpawnManager_8h-source.html +225 -219
  22. data/doc/cxxapi/StandardApplicationPool_8h-source.html +451 -445
  23. data/doc/cxxapi/SystemTime_8h-source.html +1 -1
  24. data/doc/cxxapi/Utils_8h-source.html +201 -140
  25. data/doc/cxxapi/annotated.html +2 -2
  26. data/doc/cxxapi/classClient-members.html +1 -1
  27. data/doc/cxxapi/classClient.html +1 -1
  28. data/doc/cxxapi/classHooks-members.html +1 -1
  29. data/doc/cxxapi/classHooks.html +1 -1
  30. data/doc/cxxapi/classPassenger_1_1Application-members.html +1 -1
  31. data/doc/cxxapi/classPassenger_1_1Application.html +1 -1
  32. data/doc/cxxapi/classPassenger_1_1ApplicationPool-members.html +1 -1
  33. data/doc/cxxapi/classPassenger_1_1ApplicationPool.html +1 -1
  34. data/doc/cxxapi/classPassenger_1_1ApplicationPoolServer-members.html +1 -1
  35. data/doc/cxxapi/classPassenger_1_1ApplicationPoolServer.html +1 -1
  36. data/doc/cxxapi/classPassenger_1_1ApplicationPool__inherit__graph.png +0 -0
  37. data/doc/cxxapi/classPassenger_1_1Application_1_1Session-members.html +1 -1
  38. data/doc/cxxapi/classPassenger_1_1Application_1_1Session.html +1 -1
  39. data/doc/cxxapi/{classPassenger_1_1TempFile-members.html → classPassenger_1_1BufferedUpload-members.html} +5 -5
  40. data/doc/cxxapi/{classPassenger_1_1TempFile.html → classPassenger_1_1BufferedUpload.html} +33 -43
  41. data/doc/cxxapi/classPassenger_1_1BusyException-members.html +1 -1
  42. data/doc/cxxapi/classPassenger_1_1BusyException.html +1 -1
  43. data/doc/cxxapi/classPassenger_1_1ConfigurationException-members.html +1 -1
  44. data/doc/cxxapi/classPassenger_1_1ConfigurationException.html +1 -1
  45. data/doc/cxxapi/classPassenger_1_1DirectoryMapper-members.html +1 -1
  46. data/doc/cxxapi/classPassenger_1_1DirectoryMapper.html +1 -1
  47. data/doc/cxxapi/classPassenger_1_1DummySpawnManager-members.html +1 -1
  48. data/doc/cxxapi/classPassenger_1_1DummySpawnManager.html +1 -1
  49. data/doc/cxxapi/classPassenger_1_1FileChecker-members.html +1 -1
  50. data/doc/cxxapi/classPassenger_1_1FileChecker.html +2 -2
  51. data/doc/cxxapi/classPassenger_1_1FileNotFoundException-members.html +1 -1
  52. data/doc/cxxapi/classPassenger_1_1FileNotFoundException.html +1 -1
  53. data/doc/cxxapi/classPassenger_1_1FileNotFoundException__inherit__graph.png +0 -0
  54. data/doc/cxxapi/classPassenger_1_1FileSystemException-members.html +1 -1
  55. data/doc/cxxapi/classPassenger_1_1FileSystemException.html +1 -1
  56. data/doc/cxxapi/classPassenger_1_1FileSystemException__inherit__graph.png +0 -0
  57. data/doc/cxxapi/classPassenger_1_1IOException-members.html +1 -1
  58. data/doc/cxxapi/classPassenger_1_1IOException.html +1 -1
  59. data/doc/cxxapi/classPassenger_1_1IOException__inherit__graph.png +0 -0
  60. data/doc/cxxapi/classPassenger_1_1MessageChannel-members.html +1 -1
  61. data/doc/cxxapi/classPassenger_1_1MessageChannel.html +1 -1
  62. data/doc/cxxapi/classPassenger_1_1RuntimeException-members.html +1 -1
  63. data/doc/cxxapi/classPassenger_1_1RuntimeException.html +1 -1
  64. data/doc/cxxapi/classPassenger_1_1SpawnException-members.html +1 -1
  65. data/doc/cxxapi/classPassenger_1_1SpawnException.html +1 -1
  66. data/doc/cxxapi/classPassenger_1_1SpawnManager-members.html +1 -1
  67. data/doc/cxxapi/classPassenger_1_1SpawnManager.html +1 -1
  68. data/doc/cxxapi/classPassenger_1_1StandardApplicationPool-members.html +1 -1
  69. data/doc/cxxapi/classPassenger_1_1StandardApplicationPool.html +1 -1
  70. data/doc/cxxapi/classPassenger_1_1StandardApplicationPool__inherit__graph.png +0 -0
  71. data/doc/cxxapi/classPassenger_1_1SystemException-members.html +1 -1
  72. data/doc/cxxapi/classPassenger_1_1SystemException.html +1 -1
  73. data/doc/cxxapi/classPassenger_1_1SystemException__inherit__graph.png +0 -0
  74. data/doc/cxxapi/classPassenger_1_1SystemTime-members.html +1 -1
  75. data/doc/cxxapi/classPassenger_1_1SystemTime.html +1 -1
  76. data/doc/cxxapi/definitions_8h-source.html +1 -1
  77. data/doc/cxxapi/files.html +1 -1
  78. data/doc/cxxapi/functions.html +12 -11
  79. data/doc/cxxapi/functions_func.html +9 -7
  80. data/doc/cxxapi/functions_type.html +1 -1
  81. data/doc/cxxapi/functions_vars.html +2 -4
  82. data/doc/cxxapi/graph_legend.html +1 -1
  83. data/doc/cxxapi/graph_legend.png +0 -0
  84. data/doc/cxxapi/group__Configuration.html +3 -3
  85. data/doc/cxxapi/group__Configuration.png +0 -0
  86. data/doc/cxxapi/group__Core.html +1 -1
  87. data/doc/cxxapi/group__Core.png +0 -0
  88. data/doc/cxxapi/group__Exceptions.html +1 -1
  89. data/doc/cxxapi/group__Hooks.html +1 -1
  90. data/doc/cxxapi/group__Hooks.png +0 -0
  91. data/doc/cxxapi/group__Support.html +33 -16
  92. data/doc/cxxapi/hierarchy.html +2 -2
  93. data/doc/cxxapi/inherit__graph__0.png +0 -0
  94. data/doc/cxxapi/inherit__graph__1.png +0 -0
  95. data/doc/cxxapi/inherit__graph__10.map +1 -1
  96. data/doc/cxxapi/inherit__graph__10.md5 +1 -1
  97. data/doc/cxxapi/inherit__graph__10.png +0 -0
  98. data/doc/cxxapi/inherit__graph__11.map +1 -1
  99. data/doc/cxxapi/inherit__graph__11.md5 +1 -1
  100. data/doc/cxxapi/inherit__graph__11.png +0 -0
  101. data/doc/cxxapi/inherit__graph__12.map +1 -2
  102. data/doc/cxxapi/inherit__graph__12.md5 +1 -1
  103. data/doc/cxxapi/inherit__graph__12.png +0 -0
  104. data/doc/cxxapi/inherit__graph__13.map +2 -1
  105. data/doc/cxxapi/inherit__graph__13.md5 +1 -1
  106. data/doc/cxxapi/inherit__graph__13.png +0 -0
  107. data/doc/cxxapi/inherit__graph__14.map +1 -1
  108. data/doc/cxxapi/inherit__graph__14.md5 +1 -1
  109. data/doc/cxxapi/inherit__graph__14.png +0 -0
  110. data/doc/cxxapi/inherit__graph__15.map +1 -1
  111. data/doc/cxxapi/inherit__graph__15.md5 +1 -1
  112. data/doc/cxxapi/inherit__graph__15.png +0 -0
  113. data/doc/cxxapi/inherit__graph__16.map +1 -1
  114. data/doc/cxxapi/inherit__graph__16.md5 +1 -1
  115. data/doc/cxxapi/inherit__graph__16.png +0 -0
  116. data/doc/cxxapi/inherit__graph__17.map +1 -1
  117. data/doc/cxxapi/inherit__graph__17.md5 +1 -1
  118. data/doc/cxxapi/inherit__graph__17.png +0 -0
  119. data/doc/cxxapi/inherit__graph__18.map +1 -1
  120. data/doc/cxxapi/inherit__graph__18.md5 +1 -1
  121. data/doc/cxxapi/inherit__graph__18.png +0 -0
  122. data/doc/cxxapi/inherit__graph__19.map +1 -2
  123. data/doc/cxxapi/inherit__graph__19.md5 +1 -1
  124. data/doc/cxxapi/inherit__graph__19.png +0 -0
  125. data/doc/cxxapi/inherit__graph__2.png +0 -0
  126. data/doc/cxxapi/inherit__graph__20.map +2 -1
  127. data/doc/cxxapi/inherit__graph__20.md5 +1 -1
  128. data/doc/cxxapi/inherit__graph__20.png +0 -0
  129. data/doc/cxxapi/inherit__graph__21.map +1 -1
  130. data/doc/cxxapi/inherit__graph__21.md5 +1 -1
  131. data/doc/cxxapi/inherit__graph__21.png +0 -0
  132. data/doc/cxxapi/inherit__graph__3.png +0 -0
  133. data/doc/cxxapi/inherit__graph__4.png +0 -0
  134. data/doc/cxxapi/inherit__graph__5.png +0 -0
  135. data/doc/cxxapi/inherit__graph__6.png +0 -0
  136. data/doc/cxxapi/inherit__graph__7.map +1 -1
  137. data/doc/cxxapi/inherit__graph__7.md5 +1 -1
  138. data/doc/cxxapi/inherit__graph__7.png +0 -0
  139. data/doc/cxxapi/inherit__graph__8.map +1 -1
  140. data/doc/cxxapi/inherit__graph__8.md5 +1 -1
  141. data/doc/cxxapi/inherit__graph__8.png +0 -0
  142. data/doc/cxxapi/inherit__graph__9.map +1 -1
  143. data/doc/cxxapi/inherit__graph__9.md5 +1 -1
  144. data/doc/cxxapi/inherit__graph__9.png +0 -0
  145. data/doc/cxxapi/inherits.html +18 -18
  146. data/doc/cxxapi/main.html +1 -1
  147. data/doc/cxxapi/modules.html +1 -1
  148. data/doc/cxxapi/structPassenger_1_1AnythingToString-members.html +1 -1
  149. data/doc/cxxapi/structPassenger_1_1AnythingToString.html +1 -1
  150. data/doc/cxxapi/structPassenger_1_1AnythingToString_3_01vector_3_01string_01_4_01_4-members.html +1 -1
  151. data/doc/cxxapi/structPassenger_1_1AnythingToString_3_01vector_3_01string_01_4_01_4.html +1 -1
  152. data/doc/cxxapi/structPassenger_1_1PoolOptions-members.html +1 -1
  153. data/doc/cxxapi/structPassenger_1_1PoolOptions.html +1 -1
  154. data/doc/cxxapi/tree.html +4 -4
  155. data/doc/rdoc/classes/ConditionVariable.html +58 -58
  156. data/doc/rdoc/classes/Exception.html +11 -11
  157. data/doc/rdoc/classes/GC.html +4 -4
  158. data/doc/rdoc/classes/IO.html +14 -14
  159. data/doc/rdoc/classes/PhusionPassenger.html +11 -11
  160. data/doc/rdoc/classes/PhusionPassenger/AdminTools.html +1 -1
  161. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess.html +40 -39
  162. data/doc/rdoc/classes/PhusionPassenger/Application.html +14 -14
  163. data/doc/rdoc/classes/PhusionPassenger/HTMLTemplate.html +12 -12
  164. data/doc/rdoc/classes/PhusionPassenger/Railz.html +1 -1
  165. data/doc/rdoc/classes/PhusionPassenger/Utils.html +15 -8
  166. data/doc/rdoc/classes/PlatformInfo.html +257 -253
  167. data/doc/rdoc/classes/RakeExtensions.html +2 -2
  168. data/doc/rdoc/classes/Signal.html +26 -26
  169. data/doc/rdoc/created.rid +1 -1
  170. data/doc/rdoc/files/DEVELOPERS_TXT.html +1 -1
  171. data/doc/rdoc/files/README.html +1 -1
  172. data/doc/rdoc/files/ext/phusion_passenger/native_support_c.html +1 -1
  173. data/doc/rdoc/files/lib/phusion_passenger/abstract_request_handler_rb.html +1 -1
  174. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_collection_rb.html +1 -1
  175. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_rb.html +1 -1
  176. data/doc/rdoc/files/lib/phusion_passenger/admin_tools/control_process_rb.html +1 -1
  177. data/doc/rdoc/files/lib/phusion_passenger/admin_tools_rb.html +1 -1
  178. data/doc/rdoc/files/lib/phusion_passenger/application_rb.html +1 -1
  179. data/doc/rdoc/files/lib/phusion_passenger/console_text_template_rb.html +1 -1
  180. data/doc/rdoc/files/lib/phusion_passenger/constants_rb.html +1 -1
  181. data/doc/rdoc/files/lib/phusion_passenger/dependencies_rb.html +1 -1
  182. data/doc/rdoc/files/lib/phusion_passenger/events_rb.html +1 -1
  183. data/doc/rdoc/files/lib/phusion_passenger/exceptions_rb.html +1 -1
  184. data/doc/rdoc/files/lib/phusion_passenger/html_template_rb.html +1 -1
  185. data/doc/rdoc/files/lib/phusion_passenger/message_channel_rb.html +1 -1
  186. data/doc/rdoc/files/lib/phusion_passenger/platform_info_rb.html +1 -1
  187. data/doc/rdoc/files/lib/phusion_passenger/rack/application_spawner_rb.html +1 -1
  188. data/doc/rdoc/files/lib/phusion_passenger/rack/request_handler_rb.html +1 -1
  189. data/doc/rdoc/files/lib/phusion_passenger/railz/application_spawner_rb.html +1 -1
  190. data/doc/rdoc/files/lib/phusion_passenger/railz/cgi_fixed_rb.html +1 -1
  191. data/doc/rdoc/files/lib/phusion_passenger/railz/framework_spawner_rb.html +1 -1
  192. data/doc/rdoc/files/lib/phusion_passenger/railz/request_handler_rb.html +1 -1
  193. data/doc/rdoc/files/lib/phusion_passenger/simple_benchmarking_rb.html +1 -1
  194. data/doc/rdoc/files/lib/phusion_passenger/spawn_manager_rb.html +1 -1
  195. data/doc/rdoc/files/lib/phusion_passenger/utils_rb.html +9 -9
  196. data/doc/rdoc/files/lib/phusion_passenger/wsgi/application_spawner_rb.html +1 -1
  197. data/doc/rdoc/files/{lib → misc}/rake/extensions_rb.html +2 -2
  198. data/doc/rdoc/fr_file_index.html +1 -1
  199. data/doc/rdoc/fr_method_index.html +22 -22
  200. data/ext/apache2/ApplicationPoolServer.h +43 -15
  201. data/ext/apache2/ApplicationPoolServerExecutable.cpp +27 -52
  202. data/ext/apache2/CachedFileStat.h +7 -16
  203. data/ext/apache2/Configuration.h +9 -1
  204. data/ext/apache2/FileChecker.h +4 -5
  205. data/ext/apache2/Hooks.cpp +20 -22
  206. data/ext/apache2/SpawnManager.h +6 -0
  207. data/ext/apache2/StandardApplicationPool.h +6 -0
  208. data/ext/apache2/Utils.cpp +174 -16
  209. data/ext/apache2/Utils.h +99 -38
  210. data/ext/boost/cstdint.hpp +2 -1
  211. data/ext/oxt/system_calls.cpp +20 -2
  212. data/ext/oxt/system_calls.hpp +2 -0
  213. data/lib/phusion_passenger/abstract_request_handler.rb +5 -1
  214. data/lib/phusion_passenger/admin_tools.rb +1 -1
  215. data/lib/phusion_passenger/admin_tools/control_process.rb +2 -1
  216. data/lib/phusion_passenger/dependencies.rb +7 -4
  217. data/lib/phusion_passenger/platform_info.rb +8 -2
  218. data/lib/phusion_passenger/rack/application_spawner.rb +1 -1
  219. data/lib/phusion_passenger/templates/version_not_found.html.erb +9 -0
  220. data/lib/phusion_passenger/utils.rb +13 -6
  221. data/lib/phusion_passenger/wsgi/application_spawner.rb +1 -1
  222. data/{lib → misc}/rake/cplusplus.rb +0 -0
  223. data/{lib → misc}/rake/extensions.rb +0 -0
  224. data/{lib → misc}/rake/gempackagetask.rb +0 -0
  225. data/{lib → misc}/rake/packagetask.rb +0 -0
  226. data/{lib → misc}/rake/rdoctask.rb +0 -0
  227. data/misc/render_error_pages.rb +6 -5
  228. data/test/CxxTestMain.cpp +109 -7
  229. data/test/UtilsTest.cpp +61 -51
  230. data/test/config.yml.example +6 -2
  231. data/test/integration_tests.rb +4 -0
  232. data/test/ruby/abstract_request_handler_spec.rb +9 -3
  233. data/test/ruby/rack/application_spawner_spec.rb +3 -2
  234. data/test/ruby/rails/application_spawner_spec.rb +15 -4
  235. data/test/ruby/rails/framework_spawner_spec.rb +4 -2
  236. data/test/ruby/rails/spawner_error_handling_spec.rb +4 -4
  237. data/test/ruby/spawn_manager_spec.rb +22 -9
  238. data/test/ruby/utils_spec.rb +18 -12
  239. data/test/ruby/wsgi/application_spawner_spec.rb +16 -7
  240. data/test/stub/apache2/httpd.conf.erb +1 -0
  241. data/test/stub/wsgi/passenger_wsgi.pyc +0 -0
  242. metadata +1064 -1090
  243. data/doc/Users guide Apache.html +0 -3127
  244. data/doc/Users guide Nginx.html +0 -1458
@@ -403,7 +403,8 @@ private:
403
403
  UPDATE_TRACE_POINT();
404
404
  data->disconnect();
405
405
  throw IOException("The ApplicationPool server unexpectedly "
406
- "closed the connection.");
406
+ "closed the connection while we're reading a response "
407
+ "for the 'get' command.");
407
408
  }
408
409
  if (args[0] == "ok") {
409
410
  UPDATE_TRACE_POINT();
@@ -433,7 +434,8 @@ private:
433
434
  }
434
435
  if (!result) {
435
436
  throw IOException("The ApplicationPool server "
436
- "unexpectedly closed the connection.");
437
+ "unexpectedly closed the connection while "
438
+ "we're reading the error page data.");
437
439
  }
438
440
  throw SpawnException(args[1], errorPage);
439
441
  } else {
@@ -495,7 +497,7 @@ private:
495
497
  void shutdownServer() {
496
498
  TRACE_POINT();
497
499
  this_thread::disable_syscall_interruption dsi;
498
- int ret;
500
+ int ret, status;
499
501
  time_t begin;
500
502
  bool done = false;
501
503
 
@@ -514,21 +516,34 @@ private:
514
516
  * Some Apache modules fork(), but don't close file descriptors.
515
517
  * mod_wsgi is one such example. Because of that, closing serverSocket
516
518
  * won't always cause the ApplicationPool server to exit. So we send it a
519
+ * signal. This must be the same as the oxt/system_calls.hpp interruption
517
520
  * signal.
518
521
  */
519
522
  syscalls::kill(serverPid, SIGINT);
520
523
 
521
- ret = syscalls::waitpid(serverPid, NULL, WNOHANG);
524
+ ret = syscalls::waitpid(serverPid, &status, WNOHANG);
522
525
  done = ret > 0 || ret == -1;
523
526
  if (!done) {
524
527
  syscalls::usleep(100000);
525
528
  }
526
529
  }
527
530
  if (done) {
528
- P_TRACE(2, "ApplicationPoolServerExecutable exited.");
531
+ if (ret > 0) {
532
+ if (WIFEXITED(status)) {
533
+ P_TRACE(2, "ApplicationPoolServerExecutable exited with exit status " <<
534
+ WEXITSTATUS(status) << ".");
535
+ } else if (WIFSIGNALED(status)) {
536
+ P_TRACE(2, "ApplicationPoolServerExecutable exited because of signal " <<
537
+ WTERMSIG(status) << ".");
538
+ } else {
539
+ P_TRACE(2, "ApplicationPoolServerExecutable exited for an unknown reason.");
540
+ }
541
+ } else {
542
+ P_TRACE(2, "ApplicationPoolServerExecutable exited.");
543
+ }
529
544
  } else {
530
545
  P_DEBUG("ApplicationPoolServerExecutable not exited in time. Killing it...");
531
- syscalls::kill(serverPid, SIGTERM);
546
+ syscalls::kill(serverPid, SIGKILL);
532
547
  syscalls::waitpid(serverPid, NULL, 0);
533
548
  }
534
549
 
@@ -612,17 +627,12 @@ private:
612
627
  TRACE_POINT();
613
628
  char filename[PATH_MAX];
614
629
  int ret;
615
- mode_t permissions;
616
-
617
- createPassengerTempDir();
630
+ mode_t permissions = S_IRUSR | S_IWUSR;
618
631
 
619
- if (m_user.empty()) {
620
- permissions = S_IRUSR | S_IWUSR;
621
- } else {
622
- permissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
623
- }
632
+ createPassengerTempDir(getSystemTempDir(), m_user.empty(),
633
+ "nobody", geteuid(), getegid());
624
634
 
625
- snprintf(filename, sizeof(filename), "%s/status.fifo",
635
+ snprintf(filename, sizeof(filename), "%s/info/status.fifo",
626
636
  getPassengerTempDir().c_str());
627
637
  filename[PATH_MAX - 1] = '\0';
628
638
  do {
@@ -642,6 +652,24 @@ private:
642
652
  do {
643
653
  ret = chmod(filename, permissions);
644
654
  } while (ret == -1 && errno == EINTR);
655
+
656
+ // Set the FIFO's owner according to whether we're running as root
657
+ // and whether user switching is enabled.
658
+ if (geteuid() == 0 && !m_user.empty()) {
659
+ uid_t uid;
660
+ gid_t gid;
661
+
662
+ determineLowestUserAndGroup(m_user, uid, gid);
663
+ do {
664
+ ret = chown(filename, uid, gid);
665
+ } while (ret == -1 && errno == EINTR);
666
+ if (errno == -1) {
667
+ int e = errno;
668
+ P_WARN("*** WARNING: Unable to set the FIFO file '" <<
669
+ filename << "' its owner and group to that of user " <<
670
+ m_user << ": " << strerror(e) << " (" << e << ")");
671
+ }
672
+ }
645
673
  }
646
674
  }
647
675
 
@@ -164,34 +164,9 @@ private:
164
164
  */
165
165
  void lowerPrivilege(const string &username) {
166
166
  struct passwd *entry;
167
- int ret, e;
168
167
 
169
168
  entry = getpwnam(username.c_str());
170
169
  if (entry != NULL) {
171
- do {
172
- ret = chown(getPassengerTempDir().c_str(),
173
- entry->pw_uid, entry->pw_gid);
174
- } while (ret == -1 && errno == EINTR);
175
- if (ret == -1) {
176
- e = errno;
177
- P_WARN("WARNING: Unable to change owner for directory '" <<
178
- getPassengerTempDir() << "' to '" << username <<
179
- "': " << strerror(e) << " (" << e << ")");
180
- } else {
181
- do {
182
- ret = chmod(getPassengerTempDir().c_str(),
183
- S_IRUSR | S_IWUSR | S_IXUSR);
184
- } while (ret == -1 && errno == EINTR);
185
- if (ret == -1) {
186
- e = errno;
187
- P_WARN("WARNING: Unable to change "
188
- "permissions for directory " <<
189
- getPassengerTempDir() << ": " <<
190
- strerror(e) <<
191
- " (" << e << ")");
192
- }
193
- }
194
-
195
170
  if (initgroups(username.c_str(), entry->pw_gid) != 0) {
196
171
  int e = errno;
197
172
  P_WARN("WARNING: Unable to lower ApplicationPoolServerExecutable's "
@@ -221,29 +196,22 @@ private:
221
196
  }
222
197
 
223
198
  static void fatalSignalHandler(int signum) {
224
- static void (* const defaultHandler)(int) = SIG_DFL;
225
- static bool calledBefore = false;
199
+ char message[1024];
226
200
 
227
- if (calledBefore) {
228
- // If we're here then it means that this signal
229
- // handler crashed, and we weren't even able to
230
- // call write() or system()! Abort immediately:
231
- defaultHandler(signum);
232
- } else {
233
- calledBefore = true;
234
- write(STDERR_FILENO,
235
- "*** ERROR: ApplicationPoolServerExecutable received a "
236
- "fatal signal. Running gdb to obtain the backtrace:\n\n",
237
- sizeof("*** ERROR: ApplicationPoolServerExecutable caught "
238
- "fatal signal. Running gdb to obtain the backtrace:\n\n") - 1
239
- );
240
- write(STDERR_FILENO, "----------------- Begin gdb output -----------------\n",
241
- sizeof("----------------- Begin gdb output -----------------\n") - 1);
242
- system(gdbBacktraceGenerationCommandStr);
243
- write(STDERR_FILENO, "----------------- End gdb output -----------------\n",
244
- sizeof("----------------- End gdb output -----------------\n") - 1);
245
- defaultHandler(signum);
246
- }
201
+ snprintf(message, sizeof(message) - 1,
202
+ "*** ERROR: ApplicationPoolServerExecutable received fatal signal "
203
+ "%d. Running gdb to obtain the backtrace:\n\n",
204
+ signum);
205
+ message[sizeof(message) - 1] = '\0';
206
+ write(STDERR_FILENO, message, strlen(message));
207
+ write(STDERR_FILENO, "----------------- Begin gdb output -----------------\n",
208
+ sizeof("----------------- Begin gdb output -----------------\n") - 1);
209
+ system(gdbBacktraceGenerationCommandStr);
210
+ write(STDERR_FILENO, "----------------- End gdb output -----------------\n",
211
+ sizeof("----------------- End gdb output -----------------\n") - 1);
212
+
213
+ // Invoke default signal handler.
214
+ kill(getpid(), signum);
247
215
  }
248
216
 
249
217
  void setupSignalHandlers() {
@@ -259,7 +227,7 @@ private:
259
227
  FILE *f;
260
228
  string gdbCommandFile;
261
229
 
262
- gdbCommandFile = getPassengerTempDir() + "/gdb_backtrace_command.txt";
230
+ gdbCommandFile = getPassengerTempDir() + "/info/gdb_backtrace_command.txt";
263
231
  f = fopen(gdbCommandFile.c_str(), "w");
264
232
  if (f != NULL) {
265
233
  // Write a file which contains commands for gdb to obtain
@@ -276,12 +244,17 @@ private:
276
244
 
277
245
  // Install the signal handlers.
278
246
  action.sa_handler = fatalSignalHandler;
279
- action.sa_flags = 0;
247
+ action.sa_flags = SA_RESETHAND;
280
248
  sigemptyset(&action.sa_mask);
281
- sigaction(SIGSEGV, &action, NULL);
249
+ sigaction(SIGQUIT, &action, NULL);
250
+ sigaction(SIGILL, &action, NULL);
282
251
  sigaction(SIGABRT, &action, NULL);
283
- sigaction(SIGILL, &action, NULL);
284
- sigaction(SIGFPE, &action, NULL);
252
+ sigaction(SIGFPE, &action, NULL);
253
+ sigaction(SIGBUS, &action, NULL);
254
+ sigaction(SIGSEGV, &action, NULL);
255
+ sigaction(SIGALRM, &action, NULL);
256
+ sigaction(SIGUSR1, &action, NULL);
257
+ sigaction(SIGUSR2, &action, NULL);
285
258
  }
286
259
  }
287
260
 
@@ -299,6 +272,8 @@ public:
299
272
  this->serverSocket = serverSocket;
300
273
  this->statusReportFIFO = statusReportFIFO;
301
274
  this->user = user;
275
+
276
+ P_TRACE(2, "ApplicationPoolServerExecutable initialized (PID " << getpid() << ")");
302
277
  }
303
278
 
304
279
  ~Server() {
@@ -103,28 +103,19 @@ public:
103
103
  * @return 0 if the stat() call succeeded or if no stat() was performed,
104
104
  * -1 if something went wrong while statting the file. In the latter
105
105
  * case, <tt>errno</tt> will be populated with an appropriate error code.
106
- * @throws SystemException Something went wrong while retrieving the system time.
106
+ * @throws SystemException Something went wrong while retrieving the
107
+ * system time. stat() errors will <em>not</em> result in SystemException
108
+ * being thrown.
107
109
  * @throws boost::thread_interrupted
108
110
  */
109
111
  int refresh(unsigned int throttleRate) {
110
112
  time_t currentTime;
111
- int ret;
112
113
 
113
114
  if (expired(last_time, throttleRate, currentTime)) {
114
- ret = stat(filename.c_str(), &info);
115
- if (ret == -1 && errno == EINTR) {
116
- /* If the stat() call was interrupted, then don't
117
- * update any state so that the caller can call
118
- * this function again without us returning a
119
- * cached EINTR error.
120
- */
121
- return -1;
122
- } else {
123
- last_result = ret;
124
- last_errno = errno;
125
- last_time = currentTime;
126
- return ret;
127
- }
115
+ last_result = syscalls::stat(filename.c_str(), &info);
116
+ last_errno = errno;
117
+ last_time = currentTime;
118
+ return last_result;
128
119
  } else {
129
120
  errno = last_errno;
130
121
  return last_result;
@@ -41,7 +41,7 @@
41
41
  */
42
42
 
43
43
  /** Module version number. */
44
- #define PASSENGER_VERSION "2.1.2"
44
+ #define PASSENGER_VERSION "2.1.3"
45
45
 
46
46
  #ifdef __cplusplus
47
47
  #include <set>
@@ -301,6 +301,14 @@
301
301
  return "nobody";
302
302
  }
303
303
  }
304
+
305
+ const char *getTempDir() const {
306
+ if (tempDir != NULL) {
307
+ return tempDir;
308
+ } else {
309
+ return getSystemTempDir();
310
+ }
311
+ }
304
312
  };
305
313
  }
306
314
 
@@ -77,7 +77,9 @@ public:
77
77
  * @param throttleRate When set to a non-zero value, throttling will be
78
78
  * enabled. stat() will be called at most once per
79
79
  * throttleRate seconds.
80
- * @throws SystemException Something went wrong.
80
+ * @throws SystemException Something went wrong while retrieving the
81
+ * system time. stat() errors will <em>not</em> result in SystemException
82
+ * being thrown.
81
83
  * @throws boost::thread_interrupted
82
84
  */
83
85
  bool changed(unsigned int throttleRate = 0) {
@@ -85,10 +87,7 @@ public:
85
87
  time_t ctime, mtime;
86
88
  bool result;
87
89
 
88
- do {
89
- ret = cstat.refresh(throttleRate);
90
- } while (ret == -1 && errno == EINTR);
91
-
90
+ ret = cstat.refresh(throttleRate);
92
91
  if (ret == -1) {
93
92
  ctime = 0;
94
93
  mtime = 0;
@@ -53,6 +53,7 @@
53
53
  #include <apr_pools.h>
54
54
  #include <apr_strings.h>
55
55
  #include <apr_lib.h>
56
+ #include <unixd.h>
56
57
 
57
58
  using namespace std;
58
59
  using namespace Passenger;
@@ -403,7 +404,7 @@ private:
403
404
  apr_bucket *b;
404
405
  Application::SessionPtr session;
405
406
  bool expectingUploadData;
406
- shared_ptr<TempFile> uploadData;
407
+ shared_ptr<BufferedUpload> uploadData;
407
408
 
408
409
  expectingUploadData = ap_should_client_block(r);
409
410
  if (expectingUploadData && atol(lookupHeader(r, "Content-Length"))
@@ -490,11 +491,12 @@ private:
490
491
  // The API documentation for ap_scan_script_err_brigade() says it
491
492
  // returns HTTP_OK on success, but it actually returns OK.
492
493
 
493
- // Manually set the Status header because
494
- // ap_scan_script_header_err_brigade() filters it
495
- // out. Some broken HTTP clients depend on the
496
- // Status header for retrieving the HTTP status.
497
- if (!r->status_line && *r->status_line == '\0') {
494
+ /* Manually set the Status header because
495
+ * ap_scan_script_header_err_brigade() filters it
496
+ * out. Some broken HTTP clients depend on the
497
+ * Status header for retrieving the HTTP status.
498
+ */
499
+ if (!r->status_line || *r->status_line == '\0') {
498
500
  r->status_line = apr_psprintf(r->pool,
499
501
  "%d Unknown Status",
500
502
  r->status);
@@ -680,9 +682,9 @@ private:
680
682
  return APR_SUCCESS;
681
683
  }
682
684
 
683
- shared_ptr<TempFile> receiveRequestBody(request_rec *r) {
685
+ shared_ptr<BufferedUpload> receiveRequestBody(request_rec *r) {
684
686
  TRACE_POINT();
685
- shared_ptr<TempFile> tempFile(new TempFile());
687
+ shared_ptr<BufferedUpload> tempFile(new BufferedUpload());
686
688
  char buf[1024 * 32];
687
689
  apr_off_t len;
688
690
  size_t total_written = 0;
@@ -696,7 +698,7 @@ private:
696
698
  string message("An error occured while "
697
699
  "buffering HTTP upload data to "
698
700
  "a temporary file in ");
699
- message.append(getTempDir());
701
+ message.append(BufferedUpload::getDir());
700
702
  if (e == ENOSPC) {
701
703
  message.append(". Please make sure "
702
704
  "that this directory has "
@@ -723,10 +725,9 @@ private:
723
725
  return tempFile;
724
726
  }
725
727
 
726
- void sendRequestBody(request_rec *r, Application::SessionPtr &session, shared_ptr<TempFile> &uploadData) {
728
+ void sendRequestBody(request_rec *r, Application::SessionPtr &session, shared_ptr<BufferedUpload> &uploadData) {
727
729
  TRACE_POINT();
728
730
  rewind(uploadData->handle);
729
- P_DEBUG("File upload: Content-Length = " << lookupHeader(r, "Content-Length"));
730
731
  while (!feof(uploadData->handle)) {
731
732
  char buf[1024 * 32];
732
733
  size_t size;
@@ -765,29 +766,26 @@ public:
765
766
  const char *ruby, *user;
766
767
  string applicationPoolServerExe, spawnServer;
767
768
 
768
- if (config->tempDir != NULL) {
769
- setenv("TMPDIR", config->tempDir, 1);
770
- } else {
771
- unsetenv("TMPDIR");
772
- }
773
769
  /*
774
770
  * As described in the comment in init_module, upon (re)starting
775
771
  * Apache, the Hooks constructor is called twice. We unset
776
- * PHUSION_PASSENGER_TMP before calling createPassengerTmpDir()
772
+ * PASSENGER_INSTANCE_TEMP_DIR before calling createPassengerTempDir()
777
773
  * because we want the temp directory's name to contain the PID
778
774
  * of the process in which the Hooks constructor was called for
779
775
  * the second time.
780
776
  */
781
- unsetenv("PHUSION_PASSENGER_TMP");
782
- createPassengerTempDir();
777
+ unsetenv("TMPDIR");
778
+ unsetenv("PASSENGER_INSTANCE_TEMP_DIR");
779
+ createPassengerTempDir(config->getTempDir(), config->userSwitching,
780
+ config->getDefaultUser(), unixd_config.user_id,
781
+ unixd_config.group_id);
782
+ setenv("TMPDIR", (getPassengerTempDir() + "/var").c_str(), 1);
783
783
 
784
784
  ruby = (config->ruby != NULL) ? config->ruby : DEFAULT_RUBY_COMMAND;
785
785
  if (config->userSwitching) {
786
786
  user = "";
787
- } else if (config->defaultUser != NULL) {
788
- user = config->defaultUser;
789
787
  } else {
790
- user = "nobody";
788
+ user = config->getDefaultUser();
791
789
  }
792
790
 
793
791
  if (config->root == NULL) {
@@ -326,6 +326,12 @@ private:
326
326
 
327
327
  UPDATE_TRACE_POINT();
328
328
  if (args[2] == "unix") {
329
+ /* Set tighter permissions on the spawned backend process's
330
+ * Unix socket. We try to make it only readable and writable
331
+ * by the process that contains the application pool, because
332
+ * all attempts to connect to a backend process happens
333
+ * through the application pool.
334
+ */
329
335
  int ret;
330
336
  do {
331
337
  ret = chmod(args[1].c_str(), S_IRUSR | S_IWUSR);
@@ -356,6 +356,12 @@ private:
356
356
  return result.str();
357
357
  }
358
358
 
359
+ /**
360
+ * Checks whether the given application domain needs to be restarted.
361
+ *
362
+ * @throws SystemException Something went wrong while retrieving the system time.
363
+ * @throws boost::thread_interrupted
364
+ */
359
365
  bool needsRestart(const string &appRoot, Domain *domain, const PoolOptions &options) {
360
366
  return domain->alwaysRestartFileStatter.refresh(options.statThrottleRate) == 0
361
367
  || domain->restartFileChecker.changed(options.statThrottleRate);
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  #include <cassert>
22
+ #include <pwd.h>
22
23
  #include "CachedFileStat.h"
23
24
  #include "Utils.h"
24
25
 
@@ -217,8 +218,25 @@ escapeForXml(const string &input) {
217
218
  return result;
218
219
  }
219
220
 
221
+ void
222
+ determineLowestUserAndGroup(const string &user, uid_t &uid, gid_t &gid) {
223
+ struct passwd *ent;
224
+
225
+ ent = getpwnam(user.c_str());
226
+ if (ent == NULL) {
227
+ ent = getpwnam("nobody");
228
+ }
229
+ if (ent == NULL) {
230
+ uid = (uid_t) -1;
231
+ gid = (gid_t) -1;
232
+ } else {
233
+ uid = ent->pw_uid;
234
+ gid = ent->pw_gid;
235
+ }
236
+ }
237
+
220
238
  const char *
221
- getTempDir() {
239
+ getSystemTempDir() {
222
240
  const char *temp_dir = getenv("TMPDIR");
223
241
  if (temp_dir == NULL || *temp_dir == '\0') {
224
242
  temp_dir = "/tmp";
@@ -227,11 +245,11 @@ getTempDir() {
227
245
  }
228
246
 
229
247
  string
230
- getPassengerTempDir(bool bypassCache) {
248
+ getPassengerTempDir(bool bypassCache, const string &systemTempDir) {
231
249
  if (bypassCache) {
232
250
  goto calculateResult;
233
251
  } else {
234
- const char *tmp = getenv("PHUSION_PASSENGER_TMP");
252
+ const char *tmp = getenv("PASSENGER_INSTANCE_TEMP_DIR");
235
253
  if (tmp != NULL && *tmp != '\0') {
236
254
  return tmp;
237
255
  } else {
@@ -240,25 +258,141 @@ getPassengerTempDir(bool bypassCache) {
240
258
  }
241
259
 
242
260
  calculateResult:
243
- const char *temp_dir = getTempDir();
261
+ const char *temp_dir;
244
262
  char buffer[PATH_MAX];
245
263
 
264
+ if (systemTempDir.empty()) {
265
+ temp_dir = getSystemTempDir();
266
+ } else {
267
+ temp_dir = systemTempDir.c_str();
268
+ }
246
269
  snprintf(buffer, sizeof(buffer), "%s/passenger.%lu",
247
270
  temp_dir, (unsigned long) getpid());
248
271
  buffer[sizeof(buffer) - 1] = '\0';
249
- setenv("PHUSION_PASSENGER_TMP", buffer, 1);
272
+ setenv("PASSENGER_INSTANCE_TEMP_DIR", buffer, 1);
250
273
  return buffer;
251
274
  }
252
275
 
253
276
  void
254
- createPassengerTempDir() {
255
- makeDirTree(getPassengerTempDir().c_str(), "u=rwxs,g=wx,o=wx");
277
+ createPassengerTempDir(const string &systemTempDir, bool userSwitching,
278
+ const string &lowestUser, uid_t workerUid, gid_t workerGid) {
279
+ string tmpDir(getPassengerTempDir(false, systemTempDir));
280
+ uid_t lowestUid;
281
+ gid_t lowestGid;
282
+
283
+ determineLowestUserAndGroup(lowestUser, lowestUid, lowestGid);
284
+
285
+ /* Create the temp directory with the current user as owner (which
286
+ * is root if the web server was started as root). Only the owner
287
+ * may write to this directory. Everybody else may only access the
288
+ * directory. The permissions on the subdirectories will determine
289
+ * whether a user may access that specific subdirectory.
290
+ */
291
+ makeDirTree(tmpDir, "u=wxs,g=x,o=x");
292
+
293
+ /* We want this upload buffer directory to be only accessible by the web server's
294
+ * worker processs.
295
+ *
296
+ * It only makes sense to chown webserver_private to workerUid and workerGid if the web server
297
+ * is actually able to change the user of the worker processes. That is, if the web server
298
+ * is running as root.
299
+ */
300
+ if (geteuid() == 0) {
301
+ makeDirTree(tmpDir + "/webserver_private", "u=wxs,g=,o=", workerUid, workerGid);
302
+ } else {
303
+ makeDirTree(tmpDir + "/webserver_private", "u=wxs,g=,o=");
304
+ }
305
+
306
+ /* If the web server is running as root (i.e. user switching is possible to begin with)
307
+ * but user switching is off...
308
+ */
309
+ if (geteuid() == 0 && !userSwitching) {
310
+ /* ...then the 'info' subdirectory must be owned by lowestUser, so that only root
311
+ * or lowestUser can query Phusion Passenger information.
312
+ */
313
+ makeDirTree(tmpDir + "/info", "u=rwxs,g=,o=", lowestUid, lowestGid);
314
+ } else {
315
+ /* Otherwise just use the current user and the directory's owner.
316
+ * This way, only the user that the web server's control process
317
+ * is running as will be able to query information.
318
+ */
319
+ makeDirTree(tmpDir + "/info", "u=rwxs,g=,o=");
320
+ }
321
+
322
+ if (geteuid() == 0) {
323
+ if (userSwitching) {
324
+ /* If user switching is possible and turned on, then each backend
325
+ * process may be running as a different user, so the backends
326
+ * subdirectory must be world-writable. However we don't want
327
+ * everybody to be able to know the sockets' filenames, so
328
+ * the directory is not readable, not even by its owner.
329
+ */
330
+ makeDirTree(tmpDir + "/backends", "u=wxs,g=wx,o=wx");
331
+ } else {
332
+ /* If user switching is off then all backend processes will be
333
+ * running as lowestUser, so make lowestUser the owner of the
334
+ * directory. Nobody else (except root) may access this directory.
335
+ *
336
+ * The directory is not readable as a security precaution:
337
+ * nobody should be able to know the sockets' filenames without
338
+ * having access to the application pool.
339
+ */
340
+ makeDirTree(tmpDir + "/backends", "u=wxs,g=,o=", lowestUid, lowestGid);
341
+ }
342
+ } else {
343
+ /* If user switching is not possible then all backend processes will
344
+ * be running as the same user as the web server. So we'll make the
345
+ * backends subdirectory only writable by this user. Nobody else
346
+ * (except root) may access this subdirectory.
347
+ *
348
+ * The directory is not readable as a security precaution:
349
+ * nobody should be able to know the sockets' filenames without having
350
+ * access to the application pool.
351
+ */
352
+ makeDirTree(tmpDir + "/backends", "u=wxs,g=,o=");
353
+ }
354
+
355
+ if (geteuid() == 0) {
356
+ if (userSwitching) {
357
+ /* If user switching is possible and is on, then each backend
358
+ * process may be running as a different user. So make the var
359
+ * directory world-writable.
360
+ *
361
+ * The directory is not readable as a security precaution.
362
+ */
363
+ makeDirTree(tmpDir + "/var", "u=wxs,g=wx,o=wx");
364
+ } else {
365
+ /* If user switching is off then all backend processes
366
+ * will be running as lowestUser, so make lowestUser the
367
+ * owner of the var directory. Only lowestUser may access
368
+ * the directory.
369
+ *
370
+ * The directory is not readble as a security precaution.
371
+ */
372
+ makeDirTree(tmpDir + "/var", "u=wxs,g=,o=", lowestUid, lowestGid);
373
+ }
374
+ } else {
375
+ /* If user switching is not possible then all backend processes will
376
+ * be running as the same user as the web server. So we'll make the
377
+ * var subdirectory only accessible by this user. Nobody else
378
+ * (except root) may access this subdirectory.
379
+ *
380
+ * The directory is not readble as a security precaution.
381
+ */
382
+ makeDirTree(tmpDir + "/var", "u=wxs,g=,o=");
383
+ }
256
384
  }
257
385
 
258
386
  void
259
- makeDirTree(const char *path, const char *mode) {
387
+ makeDirTree(const string &path, const char *mode, uid_t owner, gid_t group) {
260
388
  char command[PATH_MAX + 10];
261
- snprintf(command, sizeof(command), "mkdir -p -m \"%s\" \"%s\"", mode, path);
389
+ struct stat buf;
390
+
391
+ if (stat(path.c_str(), &buf) == 0) {
392
+ return;
393
+ }
394
+
395
+ snprintf(command, sizeof(command), "mkdir -p -m \"%s\" \"%s\"", mode, path.c_str());
262
396
  command[sizeof(command) - 1] = '\0';
263
397
 
264
398
  int result;
@@ -269,7 +403,8 @@ makeDirTree(const char *path, const char *mode) {
269
403
  char message[1024];
270
404
  int e = errno;
271
405
 
272
- snprintf(message, sizeof(message) - 1, "Cannot create directory '%s'", path);
406
+ snprintf(message, sizeof(message) - 1, "Cannot create directory '%s'",
407
+ path.c_str());
273
408
  message[sizeof(message) - 1] = '\0';
274
409
  if (result == -1) {
275
410
  throw SystemException(message, e);
@@ -277,24 +412,47 @@ makeDirTree(const char *path, const char *mode) {
277
412
  throw IOException(message);
278
413
  }
279
414
  }
415
+
416
+ if (owner != (uid_t) -1 && group != (gid_t) -1) {
417
+ do {
418
+ result = chown(path.c_str(), owner, group);
419
+ } while (result == -1 && errno == EINTR);
420
+ if (result != 0) {
421
+ char message[1024];
422
+ int e = errno;
423
+
424
+ snprintf(message, sizeof(message) - 1,
425
+ "Cannot change the directory '%s' its UID to %lld and GID to %lld",
426
+ path.c_str(), (long long) owner, (long long) group);
427
+ message[sizeof(message) - 1] = '\0';
428
+ throw FileSystemException(message, e, path);
429
+ }
430
+ }
280
431
  }
281
432
 
282
433
  void
283
- removeDirTree(const char *path) {
284
- char command[PATH_MAX + 10];
285
- snprintf(command, sizeof(command), "rm -rf \"%s\"", path);
434
+ removeDirTree(const string &path) {
435
+ char command[PATH_MAX + 30];
436
+ int result;
437
+
438
+ snprintf(command, sizeof(command), "chmod -R u+rwx \"%s\" 2>/dev/null", path.c_str());
286
439
  command[sizeof(command) - 1] = '\0';
440
+ do {
441
+ result = system(command);
442
+ } while (result == -1 && errno == EINTR);
287
443
 
288
- int result;
444
+ snprintf(command, sizeof(command), "rm -rf \"%s\"", path.c_str());
445
+ command[sizeof(command) - 1] = '\0';
289
446
  do {
290
447
  result = system(command);
291
448
  } while (result == -1 && errno == EINTR);
292
449
  if (result == -1) {
293
450
  char message[1024];
451
+ int e = errno;
294
452
 
295
- snprintf(message, sizeof(message) - 1, "Cannot create directory '%s'", path);
453
+ snprintf(message, sizeof(message) - 1, "Cannot remove directory '%s'", path.c_str());
296
454
  message[sizeof(message) - 1] = '\0';
297
- throw IOException(message);
455
+ throw FileSystemException(message, e, path);
298
456
  }
299
457
  }
300
458