passenger 5.3.4 → 5.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +13 -0
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/build/cxx_tests.rb +12 -1
  5. data/build/misc.rb +2 -1
  6. data/build/packaging.rb +2 -0
  7. data/build/support/cplusplus.rb +2 -2
  8. data/build/support/cxx_dependency_map.rb +653 -383
  9. data/dev/configkit-schemas/index.json +105 -3
  10. data/dev/show-latest-crashlog-dir +27 -0
  11. data/resources/templates/standalone/http.erb +2 -0
  12. data/src/agent/Core/AdminPanelConnector.h +2 -2
  13. data/src/agent/Core/ApplicationPool/Context.h +5 -1
  14. data/src/agent/Core/ApplicationPool/Group.h +2 -0
  15. data/src/agent/Core/ApplicationPool/Group/LifetimeAndBasics.cpp +5 -0
  16. data/src/agent/Core/ApplicationPool/Group/Miscellaneous.cpp +2 -1
  17. data/src/agent/Core/ApplicationPool/Group/ProcessListManagement.cpp +1 -1
  18. data/src/agent/Core/ApplicationPool/Group/StateInspection.cpp +12 -19
  19. data/src/agent/Core/ApplicationPool/Options.h +35 -31
  20. data/src/agent/Core/ApplicationPool/Pool/GroupUtils.cpp +2 -1
  21. data/src/agent/Core/ApplicationPool/Socket.h +1 -1
  22. data/src/agent/Core/Config.h +38 -7
  23. data/src/agent/Core/ConfigChange.cpp +13 -1
  24. data/src/agent/Core/Controller.h +3 -1
  25. data/src/agent/Core/Controller/Config.h +14 -11
  26. data/src/agent/Core/Controller/InitRequest.cpp +6 -5
  27. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +3 -0
  28. data/src/agent/Core/CoreMain.cpp +149 -34
  29. data/src/agent/Core/OptionParser.h +12 -1
  30. data/src/agent/Core/SpawningKit/Config.h +1 -1
  31. data/src/agent/Core/SpawningKit/Context.h +7 -1
  32. data/src/agent/Core/SpawningKit/Exceptions.h +15 -12
  33. data/src/agent/Core/SpawningKit/README.md +34 -17
  34. data/src/agent/Core/SpawningKit/Spawner.h +5 -3
  35. data/src/agent/Core/SpawningKit/UserSwitchingRules.h +5 -2
  36. data/src/agent/Core/TelemetryCollector.h +674 -0
  37. data/src/agent/Shared/Fundamentals/AbortHandler.cpp +309 -83
  38. data/src/agent/Shared/Fundamentals/AbortHandler.h +18 -3
  39. data/src/agent/Watchdog/Config.h +21 -4
  40. data/src/agent/Watchdog/WatchdogMain.cpp +4 -1
  41. data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +10 -0
  42. data/src/apache2_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.cpp +5 -0
  43. data/src/apache2_module/ConfigGeneral/AutoGeneratedSetterFuncs.cpp +30 -0
  44. data/src/apache2_module/DirectoryMapper.h +24 -36
  45. data/src/apache2_module/Hooks.cpp +13 -5
  46. data/src/apache2_module/ServerConfig/AutoGeneratedManifestGeneration.cpp +20 -0
  47. data/src/apache2_module/ServerConfig/AutoGeneratedStruct.h +24 -0
  48. data/src/cxx_supportlib/AppTypeDetector/CBindings.cpp +136 -0
  49. data/src/cxx_supportlib/AppTypeDetector/CBindings.h +73 -0
  50. data/src/cxx_supportlib/{AppTypes.h → AppTypeDetector/Detector.h} +59 -132
  51. data/src/cxx_supportlib/ConfigKit/README.md +90 -2
  52. data/src/cxx_supportlib/ConfigKit/Schema.h +58 -13
  53. data/src/cxx_supportlib/ConfigKit/Store.h +128 -4
  54. data/src/cxx_supportlib/Constants.h +1 -1
  55. data/src/cxx_supportlib/ProcessManagement/Ruby.cpp +3 -3
  56. data/src/cxx_supportlib/ProcessManagement/Ruby.h +7 -2
  57. data/src/cxx_supportlib/ProcessManagement/Spawn.cpp +14 -7
  58. data/src/cxx_supportlib/ProcessManagement/Spawn.h +21 -2
  59. data/src/cxx_supportlib/ResourceLocator.h +1 -1
  60. data/src/cxx_supportlib/ServerKit/ClientRef.h +17 -7
  61. data/src/cxx_supportlib/ServerKit/HttpRequestRef.h +17 -7
  62. data/src/cxx_supportlib/Utils/IOUtils.cpp +2 -1
  63. data/src/cxx_supportlib/Utils/ProcessMetricsCollector.h +9 -6
  64. data/src/cxx_supportlib/WrapperRegistry/CBindings.cpp +85 -0
  65. data/src/cxx_supportlib/WrapperRegistry/CBindings.h +56 -0
  66. data/src/cxx_supportlib/WrapperRegistry/Entry.h +112 -0
  67. data/src/cxx_supportlib/WrapperRegistry/README.md +37 -0
  68. data/src/cxx_supportlib/WrapperRegistry/Registry.h +309 -0
  69. data/src/helper-scripts/download_binaries/extconf.rb +6 -2
  70. data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +16 -0
  71. data/src/nginx_module/ConfigGeneral/AutoGeneratedManifestDefaultsInitialization.c +6 -0
  72. data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +24 -0
  73. data/src/nginx_module/ContentHandler.c +34 -13
  74. data/src/nginx_module/ContentHandler.h +3 -3
  75. data/src/nginx_module/MainConfig/AutoGeneratedCreateFunction.c +11 -0
  76. data/src/nginx_module/MainConfig/AutoGeneratedManifestGeneration.c +23 -0
  77. data/src/nginx_module/MainConfig/AutoGeneratedStruct.h +8 -0
  78. data/src/nginx_module/config +2 -1
  79. data/src/nginx_module/ngx_http_passenger_module.c +9 -3
  80. data/src/nginx_module/ngx_http_passenger_module.h +4 -2
  81. data/src/ruby_supportlib/phusion_passenger.rb +2 -1
  82. data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +13 -0
  83. data/src/ruby_supportlib/phusion_passenger/common_library.rb +8 -5
  84. data/src/ruby_supportlib/phusion_passenger/config/download_agent_command.rb +6 -2
  85. data/src/ruby_supportlib/phusion_passenger/config/download_nginx_engine_command.rb +6 -2
  86. data/src/ruby_supportlib/phusion_passenger/native_support.rb +7 -3
  87. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +15 -0
  88. data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +11 -1
  89. data/src/ruby_supportlib/phusion_passenger/standalone/start_command/builtin_engine.rb +3 -1
  90. metadata +12 -4
  91. data/src/cxx_supportlib/AppTypes.cpp +0 -109
@@ -94,7 +94,12 @@ coreUsage() {
94
94
  printf(" Disable the periodic check and notice about\n");
95
95
  printf(" important security updates\n");
96
96
  printf(" --security-update-check-proxy PROXY\n");
97
- printf(" Use http/SOCKS proxy for the security update check:\n");
97
+ printf(" Use HTTP/SOCKS proxy for the security update check:\n");
98
+ printf(" scheme://user:password@proxy_host:proxy_port\n");
99
+ printf(" --disable-anonymous-telemetry\n");
100
+ printf(" Disable anonymous telemetry collection\n");
101
+ printf(" --anonymous-telemetry-proxy PROXY\n");
102
+ printf(" Use HTTP/SOCKS proxy for anonymous telemetry sending:\n");
98
103
  printf(" scheme://user:password@proxy_host:proxy_port\n");
99
104
  printf("\n");
100
105
  printf("Application serving options (optional):\n");
@@ -282,6 +287,12 @@ parseCoreOption(int argc, const char *argv[], int &i, Json::Value &updates) {
282
287
  } else if (p.isValueFlag(argc, i, argv[i], '\0', "--security-update-check-proxy")) {
283
288
  updates["security_update_checker_proxy_url"] = argv[i + 1];
284
289
  i += 2;
290
+ } else if (p.isFlag(argv[i], '\0', "--disable-anonymous-telemetry")) {
291
+ updates["telemetry_collector_disabled"] = true;
292
+ i++;
293
+ } else if (p.isValueFlag(argc, i, argv[i], '\0', "--anonymous-telemetry-proxy")) {
294
+ updates["telemetry_collector_proxy_url"] = argv[i + 1];
295
+ i += 2;
285
296
  } else if (p.isValueFlag(argc, i, argv[i], '\0', "--max-pool-size")) {
286
297
  updates["max_pool_size"] = atoi(argv[i + 1]);
287
298
  i += 2;
@@ -221,7 +221,7 @@ public:
221
221
  StaticString processTitle;
222
222
 
223
223
  /**
224
- * An application type name, e.g. "rack" or "node". The only use for this
224
+ * An application type name, e.g. "ruby" or "nodejs". The only use for this
225
225
  * in SpawningKit is to better format error messages.
226
226
  *
227
227
  * @hinted_parseable
@@ -36,6 +36,7 @@
36
36
  #include <ResourceLocator.h>
37
37
  #include <RandomGenerator.h>
38
38
  #include <Exceptions.h>
39
+ #include <WrapperRegistry/Registry.h>
39
40
  #include <Utils/JsonUtils.h>
40
41
  #include <ConfigKit/Store.h>
41
42
 
@@ -132,7 +133,8 @@ private:
132
133
  public:
133
134
  /****** Dependencies ******/
134
135
 
135
- ResourceLocator *resourceLocator;
136
+ const ResourceLocator *resourceLocator;
137
+ const WrapperRegistry::Registry *wrapperRegistry;
136
138
  RandomGeneratorPtr randomGenerator;
137
139
  string integrationMode;
138
140
  string instanceDir;
@@ -147,6 +149,7 @@ public:
147
149
  nextPort(0),
148
150
 
149
151
  resourceLocator(NULL),
152
+ wrapperRegistry(NULL),
150
153
  debugSupport(NULL)
151
154
  {
152
155
  vector<ConfigKit::Error> errors;
@@ -185,6 +188,9 @@ public:
185
188
  if (resourceLocator == NULL) {
186
189
  throw RuntimeException("ResourceLocator not initialized");
187
190
  }
191
+ if (wrapperRegistry == NULL) {
192
+ throw RuntimeException("WrapperRegistry not initialized");
193
+ }
188
194
  if (randomGenerator == NULL) {
189
195
  randomGenerator = boost::make_shared<RandomGenerator>();
190
196
  }
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2011-2017 Phusion Holding B.V.
3
+ * Copyright (c) 2011-2018 Phusion Holding B.V.
4
4
  *
5
5
  * "Passenger", "Phusion Passenger" and "Union Station" are registered
6
6
  * trademarks of Phusion Holding B.V.
@@ -29,6 +29,7 @@
29
29
  #include <oxt/tracable_exception.hpp>
30
30
  #include <string>
31
31
  #include <stdexcept>
32
+ #include <limits>
32
33
 
33
34
  #include <Constants.h>
34
35
  #include <Exceptions.h>
@@ -602,7 +603,7 @@ private:
602
603
  " scripts.</p>");
603
604
  break;
604
605
  case SUBPROCESS_APP_LOAD_OR_EXEC:
605
- if (config.appType == "node") {
606
+ if (config.appType == "nodejs") {
606
607
  message.append(
607
608
  "<h3>Check whether the application calls <code>http.Server.listen()</code></h3>"
608
609
  "<p>" SHORT_PROGRAM_NAME " requires that the application calls"
@@ -736,12 +737,13 @@ private:
736
737
  const char *command[] = { "/bin/sh", "-c", "ulimit -a", NULL };
737
738
  try {
738
739
  SubprocessInfo info;
739
- string result;
740
- runCommandAndCaptureOutput(command, info, result);
741
- if (result.empty()) {
742
- result.assign("Error: command 'ulimit -a' failed");
740
+ SubprocessOutput output;
741
+ runCommandAndCaptureOutput(command, info, output,
742
+ std::numeric_limits<size_t>::max());
743
+ if (output.data.empty()) {
744
+ output.data.assign("Error: command 'ulimit -a' failed");
743
745
  }
744
- return result;
746
+ return output.data;
745
747
  } catch (const SystemException &e) {
746
748
  return P_STATIC_STRING("Error: command 'ulimit -a' failed: ") + e.what();
747
749
  }
@@ -751,12 +753,13 @@ private:
751
753
  const char *command[] = { "id", "-a", NULL };
752
754
  try {
753
755
  SubprocessInfo info;
754
- string result;
755
- runCommandAndCaptureOutput(command, info, result);
756
- if (result.empty()) {
757
- result.assign("Error: command 'id -a' failed");
756
+ SubprocessOutput output;
757
+ runCommandAndCaptureOutput(command, info, output,
758
+ std::numeric_limits<size_t>::max());
759
+ if (output.data.empty()) {
760
+ output.data.assign("Error: command 'id -a' failed");
758
761
  }
759
- return result;
762
+ return output.data;
760
763
  } catch (const SystemException &e) {
761
764
  return P_STATIC_STRING("Error: command 'id -a' failed: ") + e.what();
762
765
  }
@@ -6,7 +6,7 @@ Spawning an application process is complex, involving many steps and with many f
6
6
 
7
7
  Here is how SpawningKit is used. The caller supplies various parameters such as where the application is located, what language it's written in, what environment variables to apply, etc. SpawningKit then spawns the application process, checks whether the application spawned properly or whether it encountered an error, and then either returns an object that describes the resulting process or throws an exception that describes the failure.
8
8
 
9
- Reliability and visibility are core features in SpawningKit. When SpawningKit returns, you know for sure whether the process started correctly or not. If the application did not start correctly, then the resulting exception describes the failure in a detailed enough manner that allows users to pinpoint the source of the problem. SpawningKit also enforces timeouts everywhere so that stuck processes are handled as well.
9
+ Reliability and visibility are core features of SpawningKit. When SpawningKit returns, you know for sure whether the process started correctly or not. If the application did not start correctly, then the resulting exception describes the failure in a detailed enough manner that allows users to pinpoint the source of the problem. SpawningKit also enforces timeouts everywhere so that stuck processes are handled as well.
10
10
 
11
11
  **Table of contents**:
12
12
 
@@ -17,6 +17,13 @@ Reliability and visibility are core features in SpawningKit. When SpawningKit re
17
17
  - The start command
18
18
  - Summary with examples
19
19
  * API and implementation highlights
20
+ - Context
21
+ - Spawners (high-level API)
22
+ - HandshakePrepare and HandshakePerform (low-level API)
23
+ - Configuration object
24
+ - Exception object
25
+ - Journey
26
+ - ErrorRenderer
20
27
  * Overview of the spawning journey
21
28
  - When spawning a process without a preloader
22
29
  - When starting a preloader
@@ -42,19 +49,39 @@ Reliability and visibility are core features in SpawningKit. When SpawningKit re
42
49
 
43
50
  ### Generic vs SpawningKit-enabled applications
44
51
 
52
+ All applications
53
+ |
54
+ +-- Generic applications
55
+ | (without explicit SpawningKit support; `genericApp = true`)
56
+ |
57
+ +-- SpawningKit-enabled applications
58
+ (with explicit SpawningKit support; `genericApp = false`)
59
+ |
60
+ +-- Applications with SpawningKit support automatically injected
61
+ | through a "wrapper"; no manual modifications
62
+ | (`startsUsingWrapper = true`)
63
+ |
64
+ +-- Applications manually modified with SpawningKit support
65
+ (`startsUsingWrapper = false`)
66
+
45
67
  SpawningKit can be used to spawn any web application, both those with and without explicit SpawningKit support.
46
68
 
69
+ > A generic application corresponds to setting the SpawningKit config `genericApp = true`.
70
+
47
71
  When SpawningKit is used to spawn a generic application (without explicit SpawningKit support), the only requirement is that the application can be instructed to start and to listen on a specific TCP port on localhost. The user needs to specify a command string that tells SpawningKit how that is to be done. SpawningKit then looks for a free port that the application may use and executes the application using the supplied command string, telling it to listen on that specific port. (This approach is inspired by Heroku's Procfile system.) SpawningKit waits until the application is up by pinging the port. If the application fails (e.g. by terminating early or by not responding to pings in time) then SpawningKit will abort, reporting the application's stdout and stderr output.
48
72
 
73
+ > A SpawningKit-enabled application corresponds to setting the SpawningKit config `genericApp = false`.
74
+
49
75
  Applications can also be modified with explicit SpawningKit support. Such applications can improve performance by telling SpawningKit that it wishes to listen on a Unix domain socket instead of a TCP socket; and they can provide more feedback about any spawning failures, such as with HTML-formatted error messages or by providing more information about where internally in the application or web framework the failure occurred.
50
76
 
51
77
  ### Wrappers
52
78
 
53
- In general, it is better if an application has explicit SpawningKit support, because then it is able to provide a nicer experience and better performance. But having to modify the application's code is a major hurdle.
79
+ As we said, apps with explicit SpawningKit support is preferred (nicer experience, better performance). There are two ways to add SpawningKit support to an app:
54
80
 
55
- Luckily, it is not always necessary to modify the application. Wrappers are small programs that aid in loading applications written in specific languages -- in particular interpreted languages because they allow modifying application behavior without requiring code modifications. When a wrapper is used, SpawningKit executes the wrapper, not the actual application. The wrapper loads the application and modifies its behavior in such a way that SpawningKit support is added (e.g. ability to report HTML-formatted errors), without requiring modifications to the application code.
81
+ 1. By manually modifying the application's code to add SpawningKit support.
82
+ 2. By automatically injecting SpawningKit support into the app, without any manual code modifications.
56
83
 
57
- Wrappers are only applicable to apps without explicit SpawningKit support.
84
+ Option 2 is the most desirable, and is available to apps written in interpreted languages. This works by executing the application through a *wrapper* instead of directly. The wrapper, which is typically written in the same language as the app, loads the application and injects SpawningKit support.
58
85
 
59
86
  Passenger comes with a few wrappers for specific languages, but SpawningKit itself is more generic and requires the caller to specify which wrapper to use (if at all).
60
87
 
@@ -86,15 +113,7 @@ Using the preforking technique through SpawningKit requires either application c
86
113
 
87
114
  ### The start command
88
115
 
89
- Regardless of whether SpawningKit is used to spawn an application with or without explicit SpawningKit support, and regardless of whether a wrapper is used and whether the application/wrapper can function as a preloader, SpawningKit asks the caller to supply a "start command" that tells it how to execute the wrapper or the application. SpawningKit then uses the handshaking procedure (see: "Overview of the spawning journey") to communicate with the wrapper/application whether it should start in preloader mode or not.
90
-
91
- ### Summary with examples
92
-
93
- To help you better understand the concepts, the following summarizes some of the above concepts and how they map to supportable languages.
94
-
95
- SpawningKit-enabled wrappers are included in Passenger for these languages: Ruby, Python, Node.js, Meteor, and Perl.
96
-
97
- Any existing app that accepts http requests can be used by writing a wrapper, and any new app can be written with SpawningKit compatibility to avoid the need for a wrapper.
116
+ Regardless of whether SpawningKit is used to spawn an application directly with or without explicit SpawningKit support, and regardless of whether a wrapper is used and whether the application/wrapper can function as a preloader, SpawningKit asks the caller to supply a "start command" that tells it how to execute the wrapper or the application. SpawningKit then uses the handshaking procedure (see: "Overview of the spawning journey") to communicate with the wrapper/application whether it should start in preloader mode or not.
98
117
 
99
118
 
100
119
  ## API and implementation highlights
@@ -114,7 +133,7 @@ context.integrationMode = "standalone";
114
133
  context.finalize();
115
134
  ~~~
116
135
 
117
- ### Spawners
136
+ ### Spawners (high-level API)
118
137
 
119
138
  Use Spawners to spawn application processes. There are two main types of Spawners:
120
139
 
@@ -144,7 +163,7 @@ P_WARN("Application process spawned, PID is " << result.pid);
144
163
 
145
164
  There is also a DummySpawner class, which is only used during unit tests.
146
165
 
147
- ### HandshakePrepare and HandshakePerform
166
+ ### HandshakePrepare and HandshakePerform (low-level API)
148
167
 
149
168
  Inside SmartSpawner and DirectSpawner, HandshakePrepare and HandshakePerform are used to perform a lot of the heavy lifting. See "Overview of the spawning journey" -- HandshakePrepare and HandshakePerform are responsible for most of the stuff described there.
150
169
 
@@ -152,8 +171,6 @@ In fact, DirectSpawner is just a thin wrapper around HandshakePrepare and Handsh
152
171
 
153
172
  SmartSpawner is a bit bigger because it needs to implement the whole preloading mechanism (see section "Preloaders"), but it still uses HandshakePrepare and HandshakePerform to spawn the preloader, and to negotiate with the subprocess created by the preloader.
154
173
 
155
- Here are some simplified interaction diagrams.
156
-
157
174
  ### Configuration object
158
175
 
159
176
  HandshakePrepare and HandshakePerform do not accept an ApplicationPool::Options object, but a SpawningKit::Config object. It contains the configuration that HandshakePrepare/Perform need to perform a single spawn. SmartSpawner and DirectSpawner internally convert an ApplicationPool::Options into a SpawningKit::Config.
@@ -79,7 +79,8 @@ protected:
79
79
  void setConfigFromAppPoolOptions(Config *config, Json::Value &extraArgs,
80
80
  const AppPoolOptions &options)
81
81
  {
82
- string startCommand = options.getStartCommand(*context->resourceLocator);
82
+ string startCommand = options.getStartCommand(*context->resourceLocator,
83
+ *context->wrapperRegistry);
83
84
  string envvarsData;
84
85
  try {
85
86
  envvarsData = modp::b64_decode(options.environmentVariables.data(),
@@ -99,7 +100,7 @@ protected:
99
100
  config->findFreePort = false;
100
101
  config->loadShellEnvvars = options.loadShellEnvvars;
101
102
  config->startCommand = startCommand;
102
- config->startupFile = options.getStartupFile();
103
+ config->startupFile = options.getStartupFile(*context->wrapperRegistry);
103
104
  config->appType = options.appType;
104
105
  config->appEnv = options.environment;
105
106
  config->baseURI = options.baseURI;
@@ -112,7 +113,8 @@ protected:
112
113
  config->fileDescriptorUlimit = options.fileDescriptorUlimit;
113
114
  config->startTimeoutMsec = options.startTimeout;
114
115
 
115
- UserSwitchingInfo info = prepareUserSwitching(options);
116
+ UserSwitchingInfo info = prepareUserSwitching(options,
117
+ *context->wrapperRegistry);
116
118
  config->user = info.username;
117
119
  config->group = info.groupname;
118
120
 
@@ -35,6 +35,7 @@
35
35
  #include <boost/shared_array.hpp>
36
36
  #include <oxt/backtrace.hpp>
37
37
  #include <oxt/system_calls.hpp>
38
+ #include <WrapperRegistry/Registry.h>
38
39
  #include <Exceptions.h>
39
40
  #include <Utils.h>
40
41
  #include <Core/SpawningKit/Context.h>
@@ -61,7 +62,9 @@ struct UserSwitchingInfo {
61
62
  };
62
63
 
63
64
  inline UserSwitchingInfo
64
- prepareUserSwitching(const AppPoolOptions &options) {
65
+ prepareUserSwitching(const AppPoolOptions &options,
66
+ const WrapperRegistry::Registry &wrapperRegistry)
67
+ {
65
68
  TRACE_POINT();
66
69
  UserSwitchingInfo info;
67
70
 
@@ -95,7 +98,7 @@ prepareUserSwitching(const AppPoolOptions &options) {
95
98
 
96
99
  UPDATE_TRACE_POINT();
97
100
  string defaultGroup;
98
- string startupFile = absolutizePath(options.getStartupFile(),
101
+ string startupFile = absolutizePath(options.getStartupFile(wrapperRegistry),
99
102
  absolutizePath(options.appRoot));
100
103
  struct passwd &pwd = info.lveUserPwd;
101
104
  boost::shared_array<char> &pwdBuf = info.lveUserPwdStrBuf;
@@ -0,0 +1,674 @@
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
+ #ifndef _PASSENGER_TELEMETRY_COLLECTOR_H_
27
+ #define _PASSENGER_TELEMETRY_COLLECTOR_H_
28
+
29
+ #include <string>
30
+ #include <vector>
31
+ #include <limits>
32
+ #include <cstddef>
33
+ #include <cstdlib>
34
+ #include <cassert>
35
+
36
+ #include <boost/cstdint.hpp>
37
+ #include <boost/bind.hpp>
38
+ #include <oxt/thread.hpp>
39
+ #include <oxt/backtrace.hpp>
40
+
41
+ #include <curl/curl.h>
42
+
43
+ #include <Constants.h>
44
+ #include <Exceptions.h>
45
+ #include <Core/Controller.h>
46
+ #include <LoggingKit/LoggingKit.h>
47
+ #include <ConfigKit/ConfigKit.h>
48
+ #include <Utils/Curl.h>
49
+ #include <Utils/StrIntUtils.h>
50
+
51
+ namespace Passenger {
52
+ namespace Core {
53
+
54
+ using namespace std;
55
+
56
+
57
+ class TelemetryCollector {
58
+ public:
59
+ /*
60
+ * BEGIN ConfigKit schema: Passenger::TelemetryCollector::Schema
61
+ * (do not edit: following text is automatically generated
62
+ * by 'rake configkit_schemas_inline_comments')
63
+ *
64
+ * (unknown: Passenger::TelemetryCollector::Schema not in dev/configkit-schemas/index.json
65
+ * Please run:
66
+ * touch src/schema_printer/SchemaPrinterMain.cpp.cxxcodebuilder
67
+ * rake configkit_schemas_inline_comments
68
+ * )
69
+ *
70
+ * END
71
+ */
72
+ class Schema: public ConfigKit::Schema {
73
+ private:
74
+ static void validateProxyUrl(const ConfigKit::Store &config, vector<ConfigKit::Error> &errors) {
75
+ if (config["proxy_url"].isNull()) {
76
+ return;
77
+ }
78
+ if (config["proxy_url"].asString().empty()) {
79
+ errors.push_back(ConfigKit::Error("'{{proxy_url}}', if specified, may not be empty"));
80
+ return;
81
+ }
82
+
83
+ try {
84
+ prepareCurlProxy(config["proxy_url"].asString());
85
+ } catch (const ArgumentException &e) {
86
+ errors.push_back(ConfigKit::Error(
87
+ P_STATIC_STRING("'{{proxy_url}}': ")
88
+ + e.what()));
89
+ }
90
+ }
91
+
92
+ public:
93
+ Schema() {
94
+ using namespace ConfigKit;
95
+
96
+ add("disabled", BOOL_TYPE, OPTIONAL, false);
97
+ add("url", STRING_TYPE, OPTIONAL, "https://anontelemetry.phusionpassenger.com/v1/collect.json");
98
+ // Should be in the form: scheme://user:password@proxy_host:proxy_port
99
+ add("proxy_url", STRING_TYPE, OPTIONAL);
100
+ add("ca_certificate_path", STRING_TYPE, OPTIONAL);
101
+ add("verify_server", BOOL_TYPE, OPTIONAL, true);
102
+ add("first_interval", UINT_TYPE, OPTIONAL, 2 * 60 * 60);
103
+ add("interval", UINT_TYPE, OPTIONAL, 6 * 60 * 60);
104
+ add("interval_jitter", UINT_TYPE, OPTIONAL, 2 * 60 * 60);
105
+ add("debug_curl", BOOL_TYPE, OPTIONAL, false);
106
+ add("timeout", UINT_TYPE, OPTIONAL, 180);
107
+ add("final_run_timeout", UINT_TYPE, OPTIONAL, 5);
108
+
109
+ addValidator(validateProxyUrl);
110
+
111
+ finalize();
112
+ }
113
+ };
114
+
115
+ struct ConfigRealization {
116
+ CurlProxyInfo proxyInfo;
117
+ string url;
118
+ string caCertificatePath;
119
+
120
+ ConfigRealization(const ConfigKit::Store &config)
121
+ : proxyInfo(prepareCurlProxy(config["proxy_url"].asString())),
122
+ url(config["url"].asString()),
123
+ caCertificatePath(config["ca_certificate_path"].asString())
124
+ { }
125
+
126
+ void swap(ConfigRealization &other) BOOST_NOEXCEPT_OR_NOTHROW {
127
+ proxyInfo.swap(other.proxyInfo);
128
+ url.swap(other.url);
129
+ caCertificatePath.swap(other.caCertificatePath);
130
+ }
131
+ };
132
+
133
+ struct ConfigChangeRequest {
134
+ boost::scoped_ptr<ConfigKit::Store> config;
135
+ boost::scoped_ptr<ConfigRealization> configRlz;
136
+ };
137
+
138
+ struct TelemetryData {
139
+ vector<boost::uint64_t> requestsHandled;
140
+ MonotonicTimeUsec timestamp;
141
+ };
142
+
143
+ private:
144
+ /*
145
+ * Since the telemetry collector runs in a separate thread,
146
+ * and the configuration can change while the collector is active,
147
+ * we make a copy of the current configuration at the beginning
148
+ * of each collection cycle.
149
+ */
150
+ struct SessionState {
151
+ ConfigKit::Store config;
152
+ ConfigRealization configRlz;
153
+
154
+ SessionState(const ConfigKit::Store &currentConfig,
155
+ const ConfigRealization &currentConfigRlz)
156
+ : config(currentConfig),
157
+ configRlz(currentConfigRlz)
158
+ { }
159
+ };
160
+
161
+ mutable boost::mutex configSyncher;
162
+ ConfigKit::Store config;
163
+ ConfigRealization configRlz;
164
+ TelemetryData lastTelemetryData;
165
+ oxt::thread *collectorThread;
166
+
167
+ void threadMain() {
168
+ TRACE_POINT();
169
+
170
+ {
171
+ // Sleep for a short while to allow interruption during the Apache integration
172
+ // double startup procedure, this prevents running the update check twice
173
+ boost::unique_lock<boost::mutex> l(configSyncher);
174
+ ConfigKit::Store config(this->config);
175
+ l.unlock();
176
+
177
+ unsigned int backoffSec = config["first_interval"].asUInt()
178
+ + calculateIntervalJitter(config);
179
+ P_DEBUG("Next anonymous telemetry collection in " <<
180
+ distanceOfTimeInWords(SystemTime::get() + backoffSec));
181
+ boost::this_thread::sleep_for(boost::chrono::seconds(backoffSec));
182
+ }
183
+
184
+ while (!boost::this_thread::interruption_requested()) {
185
+ UPDATE_TRACE_POINT();
186
+ unsigned int backoffSec = 0;
187
+ try {
188
+ backoffSec = runOneCycle();
189
+ } catch (const oxt::tracable_exception &e) {
190
+ P_ERROR(e.what() << "\n" << e.backtrace());
191
+ }
192
+
193
+ if (backoffSec == 0) {
194
+ boost::unique_lock<boost::mutex> l(configSyncher);
195
+ backoffSec = config["interval"].asUInt()
196
+ + calculateIntervalJitter(config);
197
+ }
198
+
199
+ UPDATE_TRACE_POINT();
200
+ P_DEBUG("Next anonymous telemetry collection in "
201
+ << distanceOfTimeInWords(SystemTime::get() + backoffSec));
202
+ boost::this_thread::sleep_for(boost::chrono::seconds(backoffSec));
203
+ }
204
+ }
205
+
206
+ static unsigned int calculateIntervalJitter(const ConfigKit::Store &config) {
207
+ unsigned int jitter = config["interval_jitter"].asUInt();
208
+ if (jitter == 0) {
209
+ return 0;
210
+ } else {
211
+ return std::rand() % jitter;
212
+ }
213
+ }
214
+
215
+ // Virtual to allow mocking in unit tests.
216
+ virtual TelemetryData collectTelemetryData(bool isFinalRun) const {
217
+ TRACE_POINT();
218
+ TelemetryData tmData;
219
+ unsigned int counter = 0;
220
+ boost::mutex syncher;
221
+ boost::condition_variable cond;
222
+
223
+ tmData.requestsHandled.resize(controllers.size(), 0);
224
+
225
+ UPDATE_TRACE_POINT();
226
+ for (unsigned int i = 0; i < controllers.size(); i++) {
227
+ if (isFinalRun) {
228
+ inspectController(&tmData, controllers[i], i, &counter,
229
+ &syncher, &cond);
230
+ } else {
231
+ controllers[i]->getContext()->libev->runLater(boost::bind(
232
+ &TelemetryCollector::inspectController, this, &tmData,
233
+ controllers[i], i, &counter, &syncher, &cond));
234
+ }
235
+ }
236
+
237
+ UPDATE_TRACE_POINT();
238
+ {
239
+ boost::unique_lock<boost::mutex> l(syncher);
240
+ while (counter != controllers.size()) {
241
+ cond.wait(l);
242
+ }
243
+ }
244
+
245
+ tmData.timestamp = SystemTime::getMonotonicUsecWithGranularity
246
+ <SystemTime::GRAN_1SEC>();
247
+ return tmData;
248
+ }
249
+
250
+ void inspectController(TelemetryData *tmData, Controller *controller,
251
+ unsigned int index, unsigned int *counter, boost::mutex *syncher,
252
+ boost::condition_variable *cond) const
253
+ {
254
+ boost::unique_lock<boost::mutex> l(*syncher);
255
+ tmData->requestsHandled[index] = controller->totalRequestsBegun;
256
+ (*counter)++;
257
+ cond->notify_one();
258
+ }
259
+
260
+ string createRequestBody(const TelemetryData &tmData) const {
261
+ Json::Value doc;
262
+ boost::uint64_t totalRequestsHandled = 0;
263
+
264
+ P_ASSERT_EQ(tmData.requestsHandled.size(),
265
+ lastTelemetryData.requestsHandled.size());
266
+
267
+ for (unsigned int i = 0; i < tmData.requestsHandled.size(); i++) {
268
+ if (tmData.requestsHandled[i] >= lastTelemetryData.requestsHandled[i]) {
269
+ totalRequestsHandled += tmData.requestsHandled[i]
270
+ - lastTelemetryData.requestsHandled[i];
271
+ } else {
272
+ // Counter overflowed
273
+ totalRequestsHandled += std::numeric_limits<boost::uint64_t>::max()
274
+ - lastTelemetryData.requestsHandled[i]
275
+ + 1
276
+ + tmData.requestsHandled[i];
277
+ }
278
+ }
279
+
280
+ doc["requests_handled"] = (Json::UInt64) totalRequestsHandled;
281
+ doc["begin_time"] = (Json::UInt64) monoTimeToRealTime(
282
+ lastTelemetryData.timestamp);
283
+ doc["end_time"] = (Json::UInt64) monoTimeToRealTime(
284
+ tmData.timestamp);
285
+ #ifdef PASSENGER_IS_ENTERPRISE
286
+ doc["edition"] = "enterprise";
287
+ #else
288
+ doc["edition"] = "oss";
289
+ #endif
290
+
291
+ return doc.toStyledString();
292
+ }
293
+
294
+ static time_t monoTimeToRealTime(MonotonicTimeUsec monoTime) {
295
+ MonotonicTimeUsec monoNow = SystemTime::getMonotonicUsecWithGranularity
296
+ <SystemTime::GRAN_1SEC>();
297
+ unsigned long long realNow = SystemTime::getUsec();
298
+ MonotonicTimeUsec diff;
299
+
300
+ if (monoNow >= monoTime) {
301
+ diff = monoNow - monoTime;
302
+ return (realNow - diff) / 1000000;
303
+ } else {
304
+ diff = monoTime - monoNow;
305
+ return (realNow + diff) / 1000000;
306
+ }
307
+ }
308
+
309
+ static CURL *prepareCurlRequest(SessionState &sessionState, bool isFinalRun,
310
+ struct curl_slist **headers, char *lastErrorMessage,
311
+ const string &requestBody, string &responseData)
312
+ {
313
+ CURL *curl;
314
+ CURLcode code;
315
+
316
+ curl = curl_easy_init();
317
+ if (curl == NULL) {
318
+ P_ERROR("Error initializing libcurl");
319
+ return NULL;
320
+ }
321
+
322
+ code = curl_easy_setopt(curl, CURLOPT_VERBOSE,
323
+ sessionState.config["debug_curl"].asBool() ? 1L : 0L);
324
+ if (code != CURLE_OK) {
325
+ goto error;
326
+ }
327
+
328
+ code = setCurlDefaultCaInfo(curl);
329
+ if (code != CURLE_OK) {
330
+ goto error;
331
+ }
332
+
333
+ code = setCurlProxy(curl, sessionState.configRlz.proxyInfo);
334
+ if (code != CURLE_OK) {
335
+ goto error;
336
+ }
337
+
338
+ code = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
339
+ if (code != CURLE_OK) {
340
+ goto error;
341
+ }
342
+
343
+ code = curl_easy_setopt(curl, CURLOPT_URL,
344
+ sessionState.configRlz.url.c_str());
345
+ if (code != CURLE_OK) {
346
+ goto error;
347
+ }
348
+
349
+ code = curl_easy_setopt(curl, CURLOPT_HTTPGET, 0);
350
+ if (code != CURLE_OK) {
351
+ goto error;
352
+ }
353
+
354
+ code = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestBody.c_str());
355
+ if (code != CURLE_OK) {
356
+ goto error;
357
+ }
358
+
359
+ code = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, requestBody.length());
360
+ if (code != CURLE_OK) {
361
+ goto error;
362
+ }
363
+
364
+ *headers = curl_slist_append(NULL, "Content-Type: application/json");
365
+ code = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, *headers);
366
+ if (code != CURLE_OK) {
367
+ goto error;
368
+ }
369
+
370
+ if (!sessionState.configRlz.caCertificatePath.empty()) {
371
+ code = curl_easy_setopt(curl, CURLOPT_CAINFO,
372
+ sessionState.configRlz.caCertificatePath.c_str());
373
+ if (code != CURLE_OK) {
374
+ goto error;
375
+ }
376
+ }
377
+
378
+ if (sessionState.config["verify_server"].asBool()) {
379
+ // These should be on by default, but make sure.
380
+ code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
381
+ if (code != CURLE_OK) {
382
+ goto error;
383
+ }
384
+ code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
385
+ if (code != CURLE_OK) {
386
+ goto error;
387
+ }
388
+ } else {
389
+ code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
390
+ if (code != CURLE_OK) {
391
+ goto error;
392
+ }
393
+ code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
394
+ if (code != CURLE_OK) {
395
+ goto error;
396
+ }
397
+ }
398
+
399
+ code = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, lastErrorMessage);
400
+ if (code != CURLE_OK) {
401
+ goto error;
402
+ }
403
+ code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveResponseBytes);
404
+ if (code != CURLE_OK) {
405
+ goto error;
406
+ }
407
+ code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
408
+ if (code != CURLE_OK) {
409
+ goto error;
410
+ }
411
+
412
+ // setopt failure(s) below don't abort the check.
413
+ if (isFinalRun) {
414
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT,
415
+ sessionState.config["final_run_timeout"].asUInt());
416
+ } else {
417
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT,
418
+ sessionState.config["timeout"].asUInt());
419
+ }
420
+
421
+ return curl;
422
+
423
+ error:
424
+ curl_easy_cleanup(curl);
425
+ curl_slist_free_all(*headers);
426
+ P_ERROR("Error setting libcurl handle parameters: " << curl_easy_strerror(code));
427
+ return NULL;
428
+ }
429
+
430
+ static size_t receiveResponseBytes(void *buffer, size_t size,
431
+ size_t nmemb, void *userData)
432
+ {
433
+ string *responseData = (string *) userData;
434
+ responseData->append((const char *) buffer, size * nmemb);
435
+ return size * nmemb;
436
+ }
437
+
438
+ // Virtual to allow mocking in unit tests.
439
+ virtual CURLcode performCurlAction(CURL *curl, const char *lastErrorMessage,
440
+ const string &_requestBody, // only used by unit tests
441
+ string &_responseData, // only used by unit tests
442
+ long &responseCode)
443
+ {
444
+ TRACE_POINT();
445
+ CURLcode code = curl_easy_perform(curl);
446
+ if (code != CURLE_OK) {
447
+ P_ERROR("Error contacting anonymous telemetry server: "
448
+ << lastErrorMessage);
449
+ return code;
450
+ }
451
+
452
+ code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
453
+ if (code != CURLE_OK) {
454
+ P_ERROR("Error querying libcurl handle for HTTP response code: "
455
+ << curl_easy_strerror(code));
456
+ return code;
457
+ }
458
+
459
+ return CURLE_OK;
460
+ }
461
+
462
+ static bool responseCodeSupported(long code) {
463
+ return code == 200 || code == 400 || code == 422 || code == 500;
464
+ }
465
+
466
+ static bool parseResponseBody(const string &responseData, Json::Value &jsonBody) {
467
+ Json::Reader reader;
468
+ if (reader.parse(responseData, jsonBody, false)) {
469
+ return true;
470
+ } else {
471
+ P_ERROR("Error in anonymous telemetry server response:"
472
+ " JSON response parse error: " << reader.getFormattedErrorMessages()
473
+ << "; data: \"" << cEscapeString(responseData) << "\"");
474
+ return false;
475
+ }
476
+ }
477
+
478
+ static bool validateResponseBody(const Json::Value &jsonBody) {
479
+ if (!jsonBody.isObject()) {
480
+ P_ERROR("Error in anonymous telemetry server response:"
481
+ " JSON response is not an object (data: "
482
+ << stringifyJson(jsonBody) << ")");
483
+ return false;
484
+ }
485
+ if (!jsonBody.isMember("data_processed")) {
486
+ P_ERROR("Error in anonymous telemetry server response:"
487
+ " JSON response must contain a 'data_processed' field (data: "
488
+ << stringifyJson(jsonBody) << ")");
489
+ return false;
490
+ }
491
+ if (!jsonBody["data_processed"].isBool()) {
492
+ P_ERROR("Error in anonymous telemetry server response:"
493
+ " 'data_processed' field must be a boolean (data: "
494
+ << stringifyJson(jsonBody) << ")");
495
+ return false;
496
+ }
497
+ if (jsonBody.isMember("backoff") && !jsonBody["backoff"].isUInt()) {
498
+ P_ERROR("Error in anonymous telemetry server response:"
499
+ " 'backoff' field must be an unsigned integer (data: "
500
+ << stringifyJson(jsonBody) << ")");
501
+ return false;
502
+ }
503
+ if (jsonBody.isMember("log_message") && !jsonBody["log_message"].isString()) {
504
+ P_ERROR("Error in anonymous telemetry server response:"
505
+ " 'log_message' field must be a string (data: "
506
+ << stringifyJson(jsonBody) << ")");
507
+ return false;
508
+ }
509
+ return true;
510
+ }
511
+
512
+ unsigned int handleResponseBody(const TelemetryData &tmData,
513
+ const Json::Value &jsonBody)
514
+ {
515
+ unsigned int backoffSec = 0;
516
+
517
+ if (jsonBody["data_processed"].asBool()) {
518
+ lastTelemetryData = tmData;
519
+ }
520
+ if (jsonBody.isMember("backoff")) {
521
+ backoffSec = jsonBody["backoff"].asUInt();
522
+ }
523
+ if (jsonBody.isMember("log_message")) {
524
+ P_NOTICE("Message from " PROGRAM_AUTHOR ": " << jsonBody["log_message"].asString());
525
+ }
526
+
527
+ return backoffSec;
528
+ }
529
+
530
+ public:
531
+ // Dependencies
532
+ vector<Controller *> controllers;
533
+
534
+ TelemetryCollector(const Schema &schema,
535
+ const Json::Value &initialConfig = Json::Value(),
536
+ const ConfigKit::Translator &translator = ConfigKit::DummyTranslator())
537
+ : config(schema, initialConfig, translator),
538
+ configRlz(config),
539
+ collectorThread(NULL)
540
+ { }
541
+
542
+ virtual ~TelemetryCollector() {
543
+ stop();
544
+ }
545
+
546
+ void initialize() {
547
+ if (controllers.empty()) {
548
+ throw RuntimeException("controllers must be initialized");
549
+ }
550
+ lastTelemetryData.requestsHandled.resize(controllers.size(), 0);
551
+ lastTelemetryData.timestamp =
552
+ SystemTime::getMonotonicUsecWithGranularity<SystemTime::GRAN_1SEC>();
553
+ }
554
+
555
+ void start() {
556
+ assert(!lastTelemetryData.requestsHandled.empty());
557
+ collectorThread = new oxt::thread(
558
+ boost::bind(&TelemetryCollector::threadMain, this),
559
+ "Telemetry collector",
560
+ 1024 * 512
561
+ );
562
+ }
563
+
564
+ void stop() {
565
+ if (collectorThread != NULL) {
566
+ collectorThread->interrupt_and_join();
567
+ delete collectorThread;
568
+ collectorThread = NULL;
569
+ }
570
+ }
571
+
572
+ unsigned int runOneCycle(bool isFinalRun = false) {
573
+ TRACE_POINT();
574
+ boost::unique_lock<boost::mutex> l(configSyncher);
575
+ SessionState sessionState(config, configRlz);
576
+ l.unlock();
577
+
578
+ if (sessionState.config["disabled"].asBool()) {
579
+ P_DEBUG("Telemetry collector disabled; not sending anonymous telemetry data");
580
+ return 0;
581
+ }
582
+
583
+ UPDATE_TRACE_POINT();
584
+ TelemetryData tmData = collectTelemetryData(isFinalRun);
585
+
586
+ UPDATE_TRACE_POINT();
587
+ CURL *curl = NULL;
588
+ CURLcode code;
589
+ struct curl_slist *headers = NULL;
590
+ string requestBody = createRequestBody(tmData);
591
+ string responseData;
592
+ char lastErrorMessage[CURL_ERROR_SIZE] = "unknown error";
593
+ Json::Value jsonBody;
594
+
595
+ curl = prepareCurlRequest(sessionState, isFinalRun, &headers,
596
+ lastErrorMessage, requestBody, responseData);
597
+ if (curl == NULL) {
598
+ // Error message already printed
599
+ goto error;
600
+ }
601
+
602
+ P_INFO("Sending anonymous telemetry data to " PROGRAM_AUTHOR);
603
+ P_DEBUG("Telemetry server URL is: " << sessionState.configRlz.url);
604
+ P_DEBUG("Telemetry data to be sent is: " << requestBody);
605
+
606
+ UPDATE_TRACE_POINT();
607
+ long responseCode;
608
+ code = performCurlAction(curl, lastErrorMessage, requestBody,
609
+ responseData, responseCode);
610
+ if (code != CURLE_OK) {
611
+ // Error message already printed
612
+ goto error;
613
+ }
614
+
615
+ UPDATE_TRACE_POINT();
616
+ P_DEBUG("Response from telemetry server: status=" << responseCode
617
+ << ", body=" << responseData);
618
+
619
+ if (!responseCodeSupported(responseCode)) {
620
+ P_ERROR("Error from anonymous telemetry server:"
621
+ " response status not supported: " << responseCode);
622
+ goto error;
623
+ }
624
+
625
+ if (!parseResponseBody(responseData, jsonBody)
626
+ || !validateResponseBody(jsonBody))
627
+ {
628
+ // Error message already printed
629
+ goto error;
630
+ }
631
+
632
+ curl_slist_free_all(headers);
633
+ curl_easy_cleanup(curl);
634
+
635
+ return handleResponseBody(tmData, jsonBody);
636
+
637
+ error:
638
+ curl_slist_free_all(headers);
639
+ if (curl != NULL) {
640
+ curl_easy_cleanup(curl);
641
+ }
642
+ return 0;
643
+ }
644
+
645
+ bool prepareConfigChange(const Json::Value &updates,
646
+ vector<ConfigKit::Error> &errors, ConfigChangeRequest &req)
647
+ {
648
+ {
649
+ boost::lock_guard<boost::mutex> l(configSyncher);
650
+ req.config.reset(new ConfigKit::Store(config, updates, errors));
651
+ }
652
+ if (errors.empty()) {
653
+ req.configRlz.reset(new ConfigRealization(*req.config));
654
+ }
655
+ return errors.empty();
656
+ }
657
+
658
+ void commitConfigChange(ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW {
659
+ boost::lock_guard<boost::mutex> l(configSyncher);
660
+ config.swap(*req.config);
661
+ configRlz.swap(*req.configRlz);
662
+ }
663
+
664
+ Json::Value inspectConfig() const {
665
+ boost::lock_guard<boost::mutex> l(configSyncher);
666
+ return config.inspect();
667
+ }
668
+ };
669
+
670
+
671
+ } // namespace Core
672
+ } // namespace Passenger
673
+
674
+ #endif /* _PASSENGER_TELEMETRY_COLLECTOR_H_ */