passenger 5.3.7 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +14 -0
  3. data/build/agent.rb +4 -2
  4. data/build/support/cxx_dependency_map.rb +134 -0
  5. data/resources/templates/standalone/server.erb +1 -0
  6. data/src/agent/AgentMain.cpp +4 -0
  7. data/src/agent/Core/ApplicationPool/Group/StateInspection.cpp +1 -1
  8. data/src/agent/Core/ApplicationPool/Options.h +7 -7
  9. data/src/agent/Core/ApplicationPool/Process.h +3 -0
  10. data/src/agent/Core/Config.h +9 -2
  11. data/src/agent/Core/Controller/Config.h +27 -6
  12. data/src/agent/Core/Controller/InitRequest.cpp +12 -7
  13. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +2 -0
  14. data/src/agent/Core/CoreMain.cpp +62 -33
  15. data/src/agent/Core/OptionParser.h +6 -0
  16. data/src/agent/Core/SpawningKit/Spawner.h +20 -5
  17. data/src/agent/Core/SpawningKit/UserSwitchingRules.h +13 -6
  18. data/src/agent/Core/TelemetryCollector.h +1 -0
  19. data/src/agent/FileReadHelper/FileReadHelperMain.cpp +198 -0
  20. data/src/agent/Watchdog/Config.h +1 -0
  21. data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +5 -0
  22. data/src/apache2_module/ConfigGeneral/AutoGeneratedSetterFuncs.cpp +15 -0
  23. data/src/apache2_module/DirConfig/AutoGeneratedCreateFunction.cpp +5 -0
  24. data/src/apache2_module/DirConfig/AutoGeneratedManifestGeneration.cpp +13 -0
  25. data/src/apache2_module/DirConfig/AutoGeneratedMergeFunction.cpp +7 -0
  26. data/src/apache2_module/DirConfig/AutoGeneratedStruct.h +13 -0
  27. data/src/apache2_module/DirectoryMapper.h +14 -3
  28. data/src/apache2_module/Hooks.cpp +15 -4
  29. data/src/cxx_supportlib/AppLocalConfigFileUtils.h +148 -0
  30. data/src/cxx_supportlib/AppTypeDetector/CBindings.cpp +12 -1
  31. data/src/cxx_supportlib/AppTypeDetector/CBindings.h +2 -0
  32. data/src/cxx_supportlib/AppTypeDetector/Detector.h +38 -4
  33. data/src/cxx_supportlib/Constants.h +1 -1
  34. data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +16 -0
  35. data/src/nginx_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.c +6 -0
  36. data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +12 -0
  37. data/src/nginx_module/Configuration.c +20 -0
  38. data/src/nginx_module/ContentHandler.c +301 -23
  39. data/src/nginx_module/ContentHandler.h +5 -0
  40. data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +10 -0
  41. data/src/nginx_module/LocationConfig/AutoGeneratedManifestGeneration.c +27 -0
  42. data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +3 -0
  43. data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +7 -0
  44. data/src/nginx_module/ngx_http_passenger_module.h +6 -1
  45. data/src/ruby_supportlib/phusion_passenger.rb +6 -5
  46. data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +6 -0
  47. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +14 -0
  48. data/src/ruby_supportlib/phusion_passenger/standalone/app_finder.rb +1 -0
  49. data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +11 -1
  50. data/src/ruby_supportlib/phusion_passenger/standalone/start_command/builtin_engine.rb +1 -0
  51. metadata +4 -2
@@ -288,6 +288,7 @@ private:
288
288
  lastTelemetryData.timestamp);
289
289
  doc["end_time"] = (Json::UInt64) monoTimeToRealTime(
290
290
  tmData.timestamp);
291
+ doc["version"] = PASSENGER_VERSION;
291
292
  #ifdef PASSENGER_IS_ENTERPRISE
292
293
  doc["edition"] = "enterprise";
293
294
  #else
@@ -0,0 +1,198 @@
1
+ /*
2
+ * Phusion Passenger - https://www.phusionpassenger.com/
3
+ * Copyright (c) 2018 Phusion Holding B.V.
4
+ *
5
+ * "Passenger", "Phusion Passenger" and "Union Station" are registered
6
+ * trademarks of Phusion Holding B.V.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ * THE SOFTWARE.
25
+ */
26
+
27
+ // A helper app that reads from an arbitrary file.
28
+ // Its main reason for existance is to allow root processes (such as the Core)
29
+ // to read from arbitrary files in a way that's safe from symlink and other
30
+ // kinds of attacks. See the documentation for safeReadFile()
31
+ // to learn more about the different types of attacks.
32
+ //
33
+ // file-read-helper is used when the caller cannot use safeReadFile(),
34
+ // e.g. when the following two conditions hold at the same time:
35
+ //
36
+ // 1. The caller does not have control over the safety of the parent
37
+ // directories leading to the file.
38
+ // 2. The caller cannot choose not to disclose the contents of the file.
39
+ //
40
+ // file-read-helper MUST be used in combination with exec-helper in order
41
+ // to lower its privilege, otherwise no protection is provided.
42
+
43
+ #include <cstdio>
44
+ #include <cstdlib>
45
+ #include <cstddef>
46
+ #include <cerrno>
47
+ #include <cstring>
48
+ #include <string>
49
+ #include <algorithm>
50
+ #include <limits>
51
+
52
+ #include <fcntl.h>
53
+ #include <unistd.h>
54
+
55
+ #include <Constants.h>
56
+ #include <ProcessManagement/Utils.h>
57
+ #include <IOTools/IOUtils.h>
58
+ #include <Utils/OptionParsing.h>
59
+
60
+ namespace Passenger {
61
+ namespace FileReadHelper {
62
+
63
+ using namespace std;
64
+
65
+
66
+ struct Options {
67
+ size_t limit;
68
+ int programArgStart;
69
+
70
+ Options()
71
+ : limit(std::numeric_limits<size_t>::max()),
72
+ programArgStart(2)
73
+ { }
74
+ };
75
+
76
+ static void
77
+ usage() {
78
+ // ....|---------------Keep output within standard terminal width (80 chars)------------|
79
+ printf("Usage: " AGENT_EXE " file-read-helper [OPTIONS...] <PATH>\n");
80
+ printf("Reads the given file with O_NONBLOCK.\n");
81
+ printf("\n");
82
+ printf("Options:\n");
83
+ printf(" --limit <SIZE> Limit the number of bytes read (default: unlimited).\n");
84
+ printf(" --help Show this help message.\n");
85
+ }
86
+
87
+ static bool
88
+ parseOption(int argc, const char *argv[], int &i, Options &options) {
89
+ OptionParser p(usage);
90
+
91
+ if (p.isValueFlag(argc, i, argv[i], '\0', "--limit")) {
92
+ options.limit = atoi(argv[i + 1]);
93
+ i += 2;
94
+ } else {
95
+ return false;
96
+ }
97
+ return true;
98
+ }
99
+
100
+ static bool
101
+ parseOptions(int argc, const char *argv[], Options &options) {
102
+ OptionParser p(usage);
103
+ int i = 2;
104
+
105
+ while (i < argc) {
106
+ if (parseOption(argc, argv, i, options)) {
107
+ continue;
108
+ } else if (p.isFlag(argv[i], 'h', "--help")) {
109
+ usage();
110
+ exit(0);
111
+ } else if (*argv[i] == '-') {
112
+ fprintf(stderr, "ERROR: unrecognized argument %s. Please type "
113
+ "'%s file-read-helper --help' for usage.\n", argv[i], argv[0]);
114
+ exit(1);
115
+ } else {
116
+ options.programArgStart = i;
117
+ return true;
118
+ }
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ int
125
+ fileReadHelperMain(int argc, char *argv[]) {
126
+ if (argc < 3) {
127
+ usage();
128
+ exit(1);
129
+ }
130
+
131
+ Options options;
132
+ if (!parseOptions(argc, (const char **) argv, options)) {
133
+ fprintf(stderr, "Error parsing arguments.\n");
134
+ usage();
135
+ exit(1);
136
+ }
137
+
138
+ if (argc != options.programArgStart + 1) {
139
+ fprintf(stderr, "ERROR: no file path given. Please type "
140
+ "'%s file-read-helper --help' for usage.\n", argv[0]);
141
+ exit(1);
142
+ }
143
+
144
+ if (geteuid() == 0) {
145
+ fprintf(stderr, "ERROR: file-read-helper cannot be run with root"
146
+ " privileges. Please use in combination with exec-helper.\n");
147
+ exit(1);
148
+ }
149
+
150
+ resetSignalHandlersAndMask();
151
+ disableMallocDebugging();
152
+
153
+ const char *path = argv[options.programArgStart];
154
+ int fd;
155
+ do {
156
+ fd = open(path, O_RDONLY | O_NONBLOCK);
157
+ } while (fd == -1 && errno == EINTR);
158
+ if (fd == -1) {
159
+ int e = errno;
160
+ fprintf(stderr, "Error opening %s for reading: %s (errno=%d)\n",
161
+ path, strerror(e), e);
162
+ exit(1);
163
+ }
164
+
165
+ size_t totalRead = 0;
166
+ char buf[1024 * 16];
167
+
168
+ while (totalRead < options.limit) {
169
+ ssize_t ret;
170
+ do {
171
+ ret = read(fd, buf,
172
+ std::min<size_t>(sizeof(buf), options.limit - totalRead));
173
+ } while (ret == -1 && errno == EINTR);
174
+ if (ret == -1) {
175
+ int e = errno;
176
+ fprintf(stderr, "Error reading from %s: %s (errno=%d)\n",
177
+ path, strerror(e), e);
178
+ exit(1);
179
+ } else if (ret == 0) {
180
+ break;
181
+ } else {
182
+ totalRead += ret;
183
+ writeExact(1, StaticString(buf, ret));
184
+ }
185
+ }
186
+
187
+ return 0;
188
+ }
189
+
190
+
191
+ } // namespace FileReadHelper
192
+ } // namespace Passenger
193
+
194
+
195
+ int
196
+ fileReadHelperMain(int argc, char *argv[]) {
197
+ return Passenger::FileReadHelper::fileReadHelperMain(argc, argv);
198
+ }
@@ -150,6 +150,7 @@ using namespace std;
150
150
  * setsid boolean - default(false)
151
151
  * show_version_in_header boolean - default(true)
152
152
  * single_app_mode_app_root string - default,read_only
153
+ * single_app_mode_app_start_command string - read_only
153
154
  * single_app_mode_app_type string - read_only
154
155
  * single_app_mode_startup_file string - read_only
155
156
  * standalone_engine string - default
@@ -107,6 +107,11 @@ extern "C" const command_rec passenger_commands[] = {
107
107
  NULL,
108
108
  RSRC_CONF | ACCESS_CONF,
109
109
  "The application's root directory."),
110
+ AP_INIT_TAKE1("PassengerAppStartCommand",
111
+ (Take1Func) cmd_passenger_app_start_command,
112
+ NULL,
113
+ RSRC_CONF | ACCESS_CONF,
114
+ "Command string for starting the application."),
110
115
  AP_INIT_TAKE1("PassengerAppType",
111
116
  (Take1Func) cmd_passenger_app_type,
112
117
  NULL,
@@ -203,6 +203,21 @@ cmd_passenger_app_root(cmd_parms *cmd, void *pcfg, const char *arg) {
203
203
  return NULL;
204
204
  }
205
205
 
206
+ static const char *
207
+ cmd_passenger_app_start_command(cmd_parms *cmd, void *pcfg, const char *arg) {
208
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_FILES);
209
+ if (err != NULL) {
210
+ return err;
211
+ }
212
+
213
+ DirConfig *config = (DirConfig *) pcfg;
214
+ config->mAppStartCommandSourceFile = cmd->directive->filename;
215
+ config->mAppStartCommandSourceLine = cmd->directive->line_num;
216
+ config->mAppStartCommandExplicitlySet = true;
217
+ config->mAppStartCommand = arg;
218
+ return NULL;
219
+ }
220
+
206
221
  static const char *
207
222
  cmd_passenger_app_type(cmd_parms *cmd, void *pcfg, const char *arg) {
208
223
  const char *err = ap_check_cmd_context(cmd, NOT_IN_FILES);
@@ -64,6 +64,9 @@ createDirConfig_autoGenerated(DirConfig *config) {
64
64
  /*
65
65
  * config->mAppRoot: default initialized
66
66
  */
67
+ /*
68
+ * config->mAppStartCommand: default initialized
69
+ */
67
70
  /*
68
71
  * config->mAppType: default initialized
69
72
  */
@@ -124,6 +127,7 @@ createDirConfig_autoGenerated(DirConfig *config) {
124
127
  config->mAppGroupNameSourceLine = 0;
125
128
  config->mAppLogFileSourceLine = 0;
126
129
  config->mAppRootSourceLine = 0;
130
+ config->mAppStartCommandSourceLine = 0;
127
131
  config->mAppTypeSourceLine = 0;
128
132
  config->mBaseURIsSourceLine = 0;
129
133
  config->mBufferResponseSourceLine = 0;
@@ -158,6 +162,7 @@ createDirConfig_autoGenerated(DirConfig *config) {
158
162
  config->mAppGroupNameExplicitlySet = false;
159
163
  config->mAppLogFileExplicitlySet = false;
160
164
  config->mAppRootExplicitlySet = false;
165
+ config->mAppStartCommandExplicitlySet = false;
161
166
  config->mAppTypeExplicitlySet = false;
162
167
  config->mBaseURIsExplicitlySet = false;
163
168
  config->mBufferResponseExplicitlySet = false;
@@ -116,6 +116,19 @@ ConfigManifestGenerator::autoGenerated_generateConfigManifestForDirConfig(server
116
116
  pdconf->mAppRoot.data(),
117
117
  pdconf->mAppRoot.data() + pdconf->mAppRoot.size());
118
118
  }
119
+ if (pdconf->mAppStartCommandExplicitlySet) {
120
+ findOrCreateAppAndLocOptionsContainers(serverRec, csconf, cdconf,
121
+ pdconf, context, &appOptionsContainer, &locOptionsContainer);
122
+ Json::Value &optionContainer = findOrCreateOptionContainer(*appOptionsContainer,
123
+ "PassengerAppStartCommand",
124
+ sizeof("PassengerAppStartCommand") - 1);
125
+ Json::Value &hierarchyMember = addOptionContainerHierarchyMember(optionContainer,
126
+ pdconf->mAppStartCommandSourceFile,
127
+ pdconf->mAppStartCommandSourceLine);
128
+ hierarchyMember["value"] = Json::Value(
129
+ pdconf->mAppStartCommand.data(),
130
+ pdconf->mAppStartCommand.data() + pdconf->mAppStartCommand.size());
131
+ }
119
132
  if (pdconf->mAppTypeExplicitlySet) {
120
133
  findOrCreateAppAndLocOptionsContainers(serverRec, csconf, cdconf,
121
134
  pdconf, context, &appOptionsContainer, &locOptionsContainer);
@@ -68,6 +68,10 @@ mergeDirConfig_autoGenerated(DirConfig *config, DirConfig *base, DirConfig *add)
68
68
  (!add->mAppRoot.empty())
69
69
  ? add->mAppRoot
70
70
  : base->mAppRoot;
71
+ config->mAppStartCommand =
72
+ (!add->mAppStartCommand.empty())
73
+ ? add->mAppStartCommand
74
+ : base->mAppStartCommand;
71
75
  config->mAppType =
72
76
  (!add->mAppType.empty())
73
77
  ? add->mAppType
@@ -186,6 +190,7 @@ mergeDirConfig_autoGenerated(DirConfig *config, DirConfig *base, DirConfig *add)
186
190
  config->mAppGroupNameSourceFile = add->mAppGroupNameSourceFile;
187
191
  config->mAppLogFileSourceFile = add->mAppLogFileSourceFile;
188
192
  config->mAppRootSourceFile = add->mAppRootSourceFile;
193
+ config->mAppStartCommandSourceFile = add->mAppStartCommandSourceFile;
189
194
  config->mAppTypeSourceFile = add->mAppTypeSourceFile;
190
195
  config->mBaseURIsSourceFile = add->mBaseURIsSourceFile;
191
196
  config->mBufferResponseSourceFile = add->mBufferResponseSourceFile;
@@ -220,6 +225,7 @@ mergeDirConfig_autoGenerated(DirConfig *config, DirConfig *base, DirConfig *add)
220
225
  config->mAppGroupNameSourceLine = add->mAppGroupNameSourceLine;
221
226
  config->mAppLogFileSourceLine = add->mAppLogFileSourceLine;
222
227
  config->mAppRootSourceLine = add->mAppRootSourceLine;
228
+ config->mAppStartCommandSourceLine = add->mAppStartCommandSourceLine;
223
229
  config->mAppTypeSourceLine = add->mAppTypeSourceLine;
224
230
  config->mBaseURIsSourceLine = add->mBaseURIsSourceLine;
225
231
  config->mBufferResponseSourceLine = add->mBufferResponseSourceLine;
@@ -254,6 +260,7 @@ mergeDirConfig_autoGenerated(DirConfig *config, DirConfig *base, DirConfig *add)
254
260
  config->mAppGroupNameExplicitlySet = add->mAppGroupNameExplicitlySet;
255
261
  config->mAppLogFileExplicitlySet = add->mAppLogFileExplicitlySet;
256
262
  config->mAppRootExplicitlySet = add->mAppRootExplicitlySet;
263
+ config->mAppStartCommandExplicitlySet = add->mAppStartCommandExplicitlySet;
257
264
  config->mAppTypeExplicitlySet = add->mAppTypeExplicitlySet;
258
265
  config->mBaseURIsExplicitlySet = add->mBaseURIsExplicitlySet;
259
266
  config->mBufferResponseExplicitlySet = add->mBufferResponseExplicitlySet;
@@ -156,6 +156,11 @@ struct AutoGeneratedDirConfig {
156
156
  */
157
157
  StaticString mAppRoot;
158
158
 
159
+ /*
160
+ * Command string for starting the application.
161
+ */
162
+ StaticString mAppStartCommand;
163
+
159
164
  /*
160
165
  * Force specific application type.
161
166
  */
@@ -242,6 +247,7 @@ struct AutoGeneratedDirConfig {
242
247
  StaticString mAppGroupNameSourceFile;
243
248
  StaticString mAppLogFileSourceFile;
244
249
  StaticString mAppRootSourceFile;
250
+ StaticString mAppStartCommandSourceFile;
245
251
  StaticString mAppTypeSourceFile;
246
252
  StaticString mGroupSourceFile;
247
253
  StaticString mMeteorAppSettingsSourceFile;
@@ -276,6 +282,7 @@ struct AutoGeneratedDirConfig {
276
282
  unsigned int mAppGroupNameSourceLine;
277
283
  unsigned int mAppLogFileSourceLine;
278
284
  unsigned int mAppRootSourceLine;
285
+ unsigned int mAppStartCommandSourceLine;
279
286
  unsigned int mAppTypeSourceLine;
280
287
  unsigned int mGroupSourceLine;
281
288
  unsigned int mMeteorAppSettingsSourceLine;
@@ -310,6 +317,7 @@ struct AutoGeneratedDirConfig {
310
317
  bool mAppGroupNameExplicitlySet: 1;
311
318
  bool mAppLogFileExplicitlySet: 1;
312
319
  bool mAppRootExplicitlySet: 1;
320
+ bool mAppStartCommandExplicitlySet: 1;
313
321
  bool mAppTypeExplicitlySet: 1;
314
322
  bool mGroupExplicitlySet: 1;
315
323
  bool mMeteorAppSettingsExplicitlySet: 1;
@@ -489,6 +497,11 @@ struct AutoGeneratedDirConfig {
489
497
  return mAppRoot;
490
498
  }
491
499
 
500
+ StaticString
501
+ getAppStartCommand() const {
502
+ return mAppStartCommand;
503
+ }
504
+
492
505
  StaticString
493
506
  getAppType() const {
494
507
  return mAppType;
@@ -77,6 +77,7 @@ private:
77
77
  request_rec *r;
78
78
  CachedFileStat *cstat;
79
79
  boost::mutex *cstatMutex;
80
+ boost::mutex *configMutex;
80
81
  const char *baseURI;
81
82
  string publicDir;
82
83
  string appRoot;
@@ -148,10 +149,16 @@ private:
148
149
 
149
150
  UPDATE_TRACE_POINT();
150
151
  AppTypeDetector::Detector detector(registry, cstat,
151
- cstatMutex, throttleRate);
152
+ cstatMutex, throttleRate, configMutex);
152
153
  AppTypeDetector::Detector::Result detectorResult;
153
154
  string appRoot;
154
- if (config->getAppType().empty()) {
155
+ // If `AppStartCommand` is set, then it means the config specified that it is
156
+ // either a generic app or a Kuria app.
157
+ if (!config->getAppStartCommand().empty()) {
158
+ appRoot = config->getAppRoot();
159
+ } else if (config->getAppType().empty()) {
160
+ // If neither `AppStartCommand` nor `AppType` are set, then
161
+ // autodetect what kind of app this is.
155
162
  if (config->getAppRoot().empty()) {
156
163
  detectorResult = detector.checkDocumentRoot(publicDir,
157
164
  baseURI != NULL,
@@ -161,6 +168,9 @@ private:
161
168
  detectorResult = detector.checkAppRoot(appRoot);
162
169
  }
163
170
  } else if (!config->getAppRoot().empty()) {
171
+ // If `AppStartCommand` is not set but `AppType` is (as well as
172
+ // the required `AppRoot`), then verify whether the given
173
+ // `AppType` value is supported and resolve aliases.
164
174
  appRoot = config->getAppRoot().toString();
165
175
  detectorResult.wrapperRegistryEntry = &registry.lookup(
166
176
  config->getAppType());
@@ -186,13 +196,14 @@ public:
186
196
  DirectoryMapper(request_rec *r, DirConfig *config,
187
197
  const WrapperRegistry::Registry &_registry,
188
198
  CachedFileStat *cstat, boost::mutex *cstatMutex,
189
- unsigned int throttleRate)
199
+ unsigned int throttleRate, boost::mutex *configMutex)
190
200
  : registry(_registry)
191
201
  {
192
202
  this->r = r;
193
203
  this->config = config;
194
204
  this->cstat = cstat;
195
205
  this->cstatMutex = cstatMutex;
206
+ this->configMutex = configMutex;
196
207
  this->throttleRate = throttleRate;
197
208
  baseURI = NULL;
198
209
  autoDetectionDone = false;
@@ -237,6 +237,7 @@ private:
237
237
  CachedFileStat cstat;
238
238
  WatchdogLauncher watchdogLauncher;
239
239
  boost::mutex cstatMutex;
240
+ boost::mutex configMutex;
240
241
 
241
242
  static Json::Value strsetToJson(const set<string> &input) {
242
243
  Json::Value result(Json::arrayValue);
@@ -446,9 +447,11 @@ private:
446
447
  TRACE_POINT();
447
448
 
448
449
  DirectoryMapper mapper(r, config, wrapperRegistry, &cstat,
449
- &cstatMutex, serverConfig.statThrottleRate);
450
+ &cstatMutex, serverConfig.statThrottleRate, &configMutex);
450
451
  try {
451
- if (mapper.getDetectorResult().isNull()) {
452
+ if (config->getAppStartCommand().empty()
453
+ && mapper.getDetectorResult().isNull())
454
+ {
452
455
  // (B) is not true.
453
456
  disableRequestNote(r);
454
457
  return false;
@@ -988,8 +991,16 @@ private:
988
991
  // Phusion Passenger options.
989
992
  addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_ROOT"),
990
993
  mapper.getAppRoot());
991
- addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_TYPE"),
992
- mapper.getDetectorResult().wrapperRegistryEntry->language);
994
+ if (!config->getAppStartCommand().empty()) {
995
+ addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_START_COMMAND"),
996
+ config->getAppStartCommand());
997
+ } else if (mapper.getDetectorResult().wrapperRegistryEntry != NULL) {
998
+ addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_TYPE"),
999
+ mapper.getDetectorResult().wrapperRegistryEntry->language);
1000
+ } else {
1001
+ addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_START_COMMAND"),
1002
+ mapper.getDetectorResult().appStartCommand);
1003
+ }
993
1004
  constructRequestHeaders_autoGenerated(r, config, result);
994
1005
 
995
1006
  /*********************/