-
Notifications
You must be signed in to change notification settings - Fork 331
Do not use java.nio in crashtracking init (premain) #11080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,9 +12,13 @@ | |||||
| import datadog.trace.util.RandomUtils; | ||||||
| import java.io.BufferedReader; | ||||||
| import java.io.BufferedWriter; | ||||||
| import java.io.File; | ||||||
| import java.io.FileInputStream; | ||||||
| import java.io.FileOutputStream; | ||||||
| import java.io.IOException; | ||||||
| import java.nio.file.Files; | ||||||
| import java.nio.file.Path; | ||||||
| import java.io.InputStreamReader; | ||||||
| import java.io.OutputStreamWriter; | ||||||
| import java.nio.charset.StandardCharsets; | ||||||
| import java.util.regex.Pattern; | ||||||
| import java.util.stream.Collectors; | ||||||
| import javax.annotation.Nullable; | ||||||
|
|
@@ -157,8 +161,8 @@ public StoredConfig build() { | |||||
|
|
||||||
| private ConfigManager() {} | ||||||
|
|
||||||
| private static String getBaseName(Path path) { | ||||||
| String filename = path.getFileName().toString(); | ||||||
| private static String getBaseName(File file) { | ||||||
| String filename = file.getName(); | ||||||
| int dotIndex = filename.lastIndexOf('.'); | ||||||
| if (dotIndex == -1) { | ||||||
| return filename; | ||||||
|
|
@@ -185,18 +189,20 @@ private static void writeEntry(BufferedWriter writer, CharSequence key, CharSequ | |||||
| writer.newLine(); | ||||||
| } | ||||||
|
|
||||||
| public static void writeConfigToPath(Path scriptPath, String... additionalEntries) { | ||||||
| String cfgFileName = getBaseName(scriptPath) + PID_PREFIX + PidHelper.getPid() + ".cfg"; | ||||||
| Path cfgPath = scriptPath.resolveSibling(cfgFileName); | ||||||
| writeConfigToFile(Config.get(), cfgPath, additionalEntries); | ||||||
| public static void writeConfigToPath(File scriptFile, String... additionalEntries) { | ||||||
| String cfgFileName = getBaseName(scriptFile) + PID_PREFIX + PidHelper.getPid() + ".cfg"; | ||||||
| File cfgFile = new File(scriptFile.getParentFile(), cfgFileName); | ||||||
| writeConfigToFile(Config.get(), cfgFile, additionalEntries); | ||||||
| } | ||||||
|
|
||||||
| // @VisibleForTesting | ||||||
| static void writeConfigToFile(Config config, Path cfgPath, String... additionalEntries) { | ||||||
| static void writeConfigToFile(Config config, File cfgFile, String... additionalEntries) { | ||||||
| final WellKnownTags wellKnownTags = config.getWellKnownTags(); | ||||||
|
|
||||||
| LOGGER.debug("Writing config file: {}", cfgPath); | ||||||
| try (BufferedWriter bw = Files.newBufferedWriter(cfgPath)) { | ||||||
| LOGGER.debug("Writing config file: {}", cfgFile); | ||||||
| try (BufferedWriter bw = | ||||||
| new BufferedWriter( | ||||||
| new OutputStreamWriter(new FileOutputStream(cfgFile), StandardCharsets.UTF_8))) { | ||||||
| for (int i = 0; i < additionalEntries.length; i += 2) { | ||||||
| writeEntry(bw, additionalEntries[i], additionalEntries[i + 1]); | ||||||
| } | ||||||
|
|
@@ -217,27 +223,21 @@ static void writeConfigToFile(Config config, Path cfgPath, String... additionalE | |||||
| new Thread( | ||||||
| AGENT_THREAD_GROUP, | ||||||
| () -> { | ||||||
| try { | ||||||
| LOGGER.debug("Deleting config file: {}", cfgPath); | ||||||
| Files.deleteIfExists(cfgPath); | ||||||
| } catch (IOException e) { | ||||||
| LOGGER.warn(SEND_TELEMETRY, "Failed deleting config file: {}", cfgPath, e); | ||||||
| } | ||||||
| LOGGER.debug("Deleting config file: {}", cfgFile); | ||||||
| cfgFile.delete(); | ||||||
| })); | ||||||
| LOGGER.debug("Config file written: {}", cfgPath); | ||||||
| LOGGER.debug("Config file written: {}", cfgFile); | ||||||
| } catch (IOException e) { | ||||||
| LOGGER.warn(SEND_TELEMETRY, "Failed writing config file: {}", cfgPath); | ||||||
| try { | ||||||
| Files.deleteIfExists(cfgPath); | ||||||
| } catch (IOException ignored) { | ||||||
| // ignore | ||||||
| } | ||||||
| LOGGER.warn(SEND_TELEMETRY, "Failed writing config file: {}", cfgFile); | ||||||
| cfgFile.delete(); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Undocumented best-effort delete The original code had an explicit
Suggested change
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| @Nullable | ||||||
| public static StoredConfig readConfig(Config config, Path scriptPath) { | ||||||
| try (final BufferedReader reader = Files.newBufferedReader(scriptPath)) { | ||||||
| public static StoredConfig readConfig(Config config, File scriptFile) { | ||||||
| try (final BufferedReader reader = | ||||||
| new BufferedReader( | ||||||
| new InputStreamReader(new FileInputStream(scriptFile), StandardCharsets.UTF_8))) { | ||||||
| final StoredConfig.Builder cfgBuilder = new StoredConfig.Builder(config); | ||||||
| String line; | ||||||
| while ((line = reader.readLine()) != null) { | ||||||
|
|
@@ -284,7 +284,7 @@ public static StoredConfig readConfig(Config config, Path scriptPath) { | |||||
| } | ||||||
| return cfgBuilder.build(); | ||||||
| } catch (Throwable t) { | ||||||
| LOGGER.error("Failed to read config file: {}", scriptPath, t); | ||||||
| LOGGER.error("Failed to read config file: {}", scriptFile, t); | ||||||
| } | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,27 +2,23 @@ | |||||||||||||||||||
|
|
||||||||||||||||||||
| import static datadog.crashtracking.ConfigManager.writeConfigToPath; | ||||||||||||||||||||
| import static datadog.crashtracking.Initializer.LOG; | ||||||||||||||||||||
| import static datadog.crashtracking.Initializer.RWXRWXRWX; | ||||||||||||||||||||
| import static datadog.crashtracking.Initializer.R_XR_XR_X; | ||||||||||||||||||||
| import static datadog.crashtracking.Initializer.findAgentJar; | ||||||||||||||||||||
| import static datadog.crashtracking.Initializer.getCrashUploaderTemplate; | ||||||||||||||||||||
| import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; | ||||||||||||||||||||
| import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute; | ||||||||||||||||||||
| import static java.nio.file.attribute.PosixFilePermissions.fromString; | ||||||||||||||||||||
| import static java.util.Locale.ROOT; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import datadog.environment.SystemProperties; | ||||||||||||||||||||
| import datadog.trace.util.PidHelper; | ||||||||||||||||||||
| import datadog.trace.util.Strings; | ||||||||||||||||||||
| import java.io.BufferedReader; | ||||||||||||||||||||
| import java.io.BufferedWriter; | ||||||||||||||||||||
| import java.io.File; | ||||||||||||||||||||
| import java.io.FileOutputStream; | ||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||
| import java.io.InputStream; | ||||||||||||||||||||
| import java.io.InputStreamReader; | ||||||||||||||||||||
| import java.nio.file.FileAlreadyExistsException; | ||||||||||||||||||||
| import java.nio.file.Files; | ||||||||||||||||||||
| import java.nio.file.Path; | ||||||||||||||||||||
| import java.nio.file.Paths; | ||||||||||||||||||||
| import java.io.OutputStreamWriter; | ||||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public final class CrashUploaderScriptInitializer { | ||||||||||||||||||||
| private static final String SETUP_FAILURE_MESSAGE = "Crash tracking will not work properly."; | ||||||||||||||||||||
|
|
@@ -54,71 +50,70 @@ static void initialize(String onErrorVal, String onErrorFile, String javacorePat | |||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Path scriptPath = Paths.get(onErrorVal.replace(" %p", "")); | ||||||||||||||||||||
| File scriptFile = new File(onErrorVal.replace(" %p", "")); | ||||||||||||||||||||
| boolean isDDCrashUploader = | ||||||||||||||||||||
| scriptPath.getFileName().toString().toLowerCase(ROOT).contains("dd_crash_uploader"); | ||||||||||||||||||||
| if (isDDCrashUploader && !copyCrashUploaderScript(scriptPath, onErrorFile, agentJar)) { | ||||||||||||||||||||
| scriptFile.getName().toLowerCase(ROOT).contains("dd_crash_uploader"); | ||||||||||||||||||||
| if (isDDCrashUploader && !copyCrashUploaderScript(scriptFile, onErrorFile, agentJar)) { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (javacorePath != null && !javacorePath.isEmpty()) { | ||||||||||||||||||||
| writeConfigToPath(scriptPath, "agent", agentJar, "javacore_path", javacorePath); | ||||||||||||||||||||
| writeConfigToPath(scriptFile, "agent", agentJar, "javacore_path", javacorePath); | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| writeConfigToPath(scriptPath, "agent", agentJar, "hs_err", onErrorFile); | ||||||||||||||||||||
| writeConfigToPath(scriptFile, "agent", agentJar, "hs_err", onErrorFile); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static boolean copyCrashUploaderScript( | ||||||||||||||||||||
| Path scriptPath, String onErrorFile, String agentJar) { | ||||||||||||||||||||
| Path scriptDirectory = scriptPath.getParent(); | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| Files.createDirectories(scriptDirectory, asFileAttribute(fromString(RWXRWXRWX))); | ||||||||||||||||||||
| } catch (UnsupportedOperationException e) { | ||||||||||||||||||||
| LOG.warn( | ||||||||||||||||||||
| SEND_TELEMETRY, | ||||||||||||||||||||
| "Unsupported permissions '" + RWXRWXRWX + "' for {}. " + SETUP_FAILURE_MESSAGE, | ||||||||||||||||||||
| scriptDirectory); | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } catch (FileAlreadyExistsException ignored) { | ||||||||||||||||||||
| // can be safely ignored; if the folder exists we will just reuse it | ||||||||||||||||||||
| if (!Files.isWritable(scriptDirectory)) { | ||||||||||||||||||||
| File scriptFile, String onErrorFile, String agentJar) { | ||||||||||||||||||||
| File scriptDirectory = scriptFile.getParentFile(); | ||||||||||||||||||||
| if (!scriptDirectory.exists()) { | ||||||||||||||||||||
| if (!scriptDirectory.mkdirs()) { | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-atomic directory creation and permission setting The original This is init-time code so the practical impact is minimal, but worth documenting the accepted trade-off with a comment. |
||||||||||||||||||||
| LOG.warn( | ||||||||||||||||||||
| SEND_TELEMETRY, "Read only directory {}. " + SETUP_FAILURE_MESSAGE, scriptDirectory); | ||||||||||||||||||||
| SEND_TELEMETRY, | ||||||||||||||||||||
| "Failed to create writable crash tracking script folder {}. " + SETUP_FAILURE_MESSAGE, | ||||||||||||||||||||
| scriptDirectory); | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||||
| LOG.warn( | ||||||||||||||||||||
| SEND_TELEMETRY, | ||||||||||||||||||||
| "Failed to create writable crash tracking script folder {}. " + SETUP_FAILURE_MESSAGE, | ||||||||||||||||||||
| scriptDirectory); | ||||||||||||||||||||
| scriptDirectory.setReadable(true, false); | ||||||||||||||||||||
| scriptDirectory.setWritable(true, false); | ||||||||||||||||||||
| scriptDirectory.setExecutable(true, false); | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return values of permission calls are ignored
Suggested change
|
||||||||||||||||||||
| } | ||||||||||||||||||||
| if (!scriptDirectory.canWrite()) { | ||||||||||||||||||||
| LOG.warn(SEND_TELEMETRY, "Read only directory {}. " + SETUP_FAILURE_MESSAGE, scriptDirectory); | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| LOG.debug("Writing crash uploader script: {}", scriptPath); | ||||||||||||||||||||
| writeCrashUploaderScript(getCrashUploaderTemplate(), scriptPath, agentJar, onErrorFile); | ||||||||||||||||||||
| LOG.debug("Writing crash uploader script: {}", scriptFile); | ||||||||||||||||||||
| writeCrashUploaderScript(getCrashUploaderTemplate(), scriptFile, agentJar, onErrorFile); | ||||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||||
| LOG.warn( | ||||||||||||||||||||
| SEND_TELEMETRY, | ||||||||||||||||||||
| "Failed to copy crash tracking script {}. " + SETUP_FAILURE_MESSAGE, | ||||||||||||||||||||
| scriptPath); | ||||||||||||||||||||
| scriptFile); | ||||||||||||||||||||
| return false; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static void writeCrashUploaderScript( | ||||||||||||||||||||
| InputStream template, Path scriptPath, String execClass, String crashFile) | ||||||||||||||||||||
| InputStream template, File scriptFile, String execClass, String crashFile) | ||||||||||||||||||||
| throws IOException { | ||||||||||||||||||||
| if (!Files.exists(scriptPath)) { | ||||||||||||||||||||
| if (!scriptFile.exists()) { | ||||||||||||||||||||
| try (BufferedReader br = new BufferedReader(new InputStreamReader(template)); | ||||||||||||||||||||
| BufferedWriter bw = Files.newBufferedWriter(scriptPath)) { | ||||||||||||||||||||
| BufferedWriter bw = | ||||||||||||||||||||
| new BufferedWriter( | ||||||||||||||||||||
| new OutputStreamWriter( | ||||||||||||||||||||
| new FileOutputStream(scriptFile), StandardCharsets.UTF_8))) { | ||||||||||||||||||||
| String line; | ||||||||||||||||||||
| while ((line = br.readLine()) != null) { | ||||||||||||||||||||
| bw.write(template(line, execClass, crashFile)); | ||||||||||||||||||||
| bw.newLine(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| Files.setPosixFilePermissions(scriptPath, fromString(R_XR_XR_X)); | ||||||||||||||||||||
| scriptFile.setReadable(true, false); | ||||||||||||||||||||
| scriptFile.setWritable(false, false); | ||||||||||||||||||||
| scriptFile.setExecutable(true, false); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Silent failure on config file deletion
The original
Files.deleteIfExists()call logged aWARNwithSEND_TELEMETRYon failure. The replacementcfgFile.delete()returnsfalseon failure but the return value is unchecked, silently dropping that telemetry signal.