summaryrefslogtreecommitdiff
path: root/error.c
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2023-08-01 04:12:22 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2023-09-25 22:57:28 +0900
commit89b3c111ff0b846d9ded0cff3ca08cb566c6cc25 (patch)
treef85dce8914ce1394d2665e8c313dcad2b1c45191 /error.c
parent85984a53e87dfbccb52aba952367fddea4a77870 (diff)
Redirect to RUBY_BUGREPORT_PATH file
Diffstat (limited to 'error.c')
-rw-r--r--error.c180
1 files changed, 175 insertions, 5 deletions
diff --git a/error.c b/error.c
index 3aac9a3d87..140854c950 100644
--- a/error.c
+++ b/error.c
@@ -622,18 +622,187 @@ rb_bug_reporter_add(void (*func)(FILE *, void *), void *data)
return 1;
}
+/* returns true if x can not be used as file name */
+static bool
+path_sep_p(char x)
+{
+#if defined __CYGWIN__ || defined DOSISH
+# define PATH_SEP_ENCODING 1
+ // Assume that "/" is only the first byte in any encoding.
+ if (x == ':') return true; // drive letter or ADS
+ if (x == '\\') return true;
+#endif
+ return x == '/';
+}
+
+struct path_string {
+ const char *ptr;
+ size_t len;
+};
+
+static const char PATHSEP_REPLACE = '!';
+
+static char *
+append_pathname(char *p, const char *pe, VALUE str)
+{
+#ifdef PATH_SEP_ENCODING
+ rb_encoding *enc = rb_enc_get(str);
+#endif
+ const char *s = RSTRING_PTR(str);
+ const char *const se = s + RSTRING_LEN(str);
+ char c;
+
+ --pe; // for terminator
+
+ while (p < pe && s < se && (c = *s) != '\0') {
+ if (c == '.') {
+ if (s == se || !*s) break; // chomp "." basename
+ if (path_sep_p(s[1])) goto skipsep; // skip "./"
+ }
+ else if (path_sep_p(c)) {
+ // squeeze successive separators
+ *p++ = PATHSEP_REPLACE;
+ skipsep:
+ while (++s < se && path_sep_p(*s));
+ continue;
+ }
+ const char *const ss = s;
+ while (p < pe && s < se && *s && !path_sep_p(*s)) {
+#ifdef PATH_SEP_ENCODING
+ int n = rb_enc_mbclen(s, se, enc);
+#else
+ const int n = 1;
+#endif
+ p += n;
+ s += n;
+ }
+ if (s > ss) memcpy(p - (s - ss), ss, s - ss);
+ }
+
+ return p;
+}
+
+static char *
+append_basename(char *p, const char *pe, struct path_string *path, VALUE str)
+{
+ if (!path->ptr) {
+#ifdef PATH_SEP_ENCODING
+ rb_encoding *enc = rb_enc_get(str);
+#endif
+ const char *const b = RSTRING_PTR(str), *const e = RSTRING_END(str), *p = e;
+
+ while (p > b) {
+ if (path_sep_p(p[-1])) {
+#ifdef PATH_SEP_ENCODING
+ const char *t = rb_enc_prev_char(b, p, e, enc);
+ if (t == p-1) break;
+ p = t;
+#else
+ break;
+#endif
+ }
+ else {
+ --p;
+ }
+ }
+
+ path->ptr = p;
+ path->len = e - p;
+ }
+ size_t n = path->len;
+ if (p + n > pe) n = pe - p;
+ memcpy(p, path->ptr, n);
+ return p + n;
+}
+
+static void
+finish_report(FILE *out)
+{
+ if (out != stdout && out != stderr) fclose(out);
+}
+
+/*
+ * Open a bug report file to write. The `RUBY_BUGREPORT_PATH`
+ * environment variable can be set to define a template that is used
+ * to name bug report files. The template can contain % specifiers
+ * which are substituted by the following values when a bug report
+ * file is created:
+ *
+ * %% A single % character.
+ * %e The base name of the executable filename.
+ * %E Pathname of executable, with slashes ('/') replaced by
+ * exclamation marks ('!').
+ * %f Similar to %e with the main script filename.
+ * %F Similar to %E with the main script filename.
+ * %p PID of dumped process in decimal.
+ * %t Time of dump, expressed as seconds since the Epoch,
+ * 1970-01-01 00:00:00 +0000 (UTC).
+ */
+static FILE *
+open_report_path(const char *template, char *buf, size_t size)
+{
+ if (template && *template) {
+ char *p = buf;
+ char *end = buf + size;
+ rb_pid_t pid = 0;
+ struct path_string exe = {0};
+ struct path_string script = {0};
+ time_t t = 0;
+ while (p < end-1 && *template) {
+ char c = *template++;
+ if (c == '%') {
+ switch (c = *template++) {
+ case 'e':
+ p = append_basename(p, end, &exe, rb_argv0);
+ continue;
+ case 'E':
+ p = append_pathname(p, end, rb_argv0);
+ continue;
+ case 'f':
+ p = append_basename(p, end, &script, GET_VM()->orig_progname);
+ continue;
+ case 'F':
+ p = append_pathname(p, end, GET_VM()->orig_progname);
+ continue;
+ case 'p':
+ if (!pid) pid = getpid();
+ snprintf(p, end-p, "%" PRI_PIDT_PREFIX "d", pid);
+ p += strlen(p);
+ continue;
+ case 't':
+ if (!t) t = time(NULL);
+ snprintf(p, end-p, "%" PRI_TIMET_PREFIX "d", t);
+ p += strlen(p);
+ continue;
+ }
+ }
+ *p++ = c;
+ }
+ *p = '\0';
+ if (0) fprintf(stderr, "RUBY_BUGREPORT_PATH=%s\n", buf);
+ return fopen(buf, "w");
+ }
+ return NULL;
+}
+
/* SIGSEGV handler might have a very small stack. Thus we need to use it carefully. */
#define REPORT_BUG_BUFSIZ 256
static FILE *
bug_report_file(const char *file, int line)
{
char buf[REPORT_BUG_BUFSIZ];
- FILE *out = stderr;
+ FILE *out = open_report_path(getenv("RUBY_BUGREPORT_PATH"), buf, sizeof(buf));
int len = err_position_0(buf, sizeof(buf), file, line);
- if ((ssize_t)fwrite(buf, 1, len, out) == (ssize_t)len ||
- (ssize_t)fwrite(buf, 1, len, (out = stdout)) == (ssize_t)len) {
- return out;
+ if (out) {
+ if ((ssize_t)fwrite(buf, 1, len, out) == (ssize_t)len) return out;
+ fclose(out);
+ }
+ if ((ssize_t)fwrite(buf, 1, len, stderr) == (ssize_t)len) {
+ return stderr;
+ }
+ if ((ssize_t)fwrite(buf, 1, len, stdout) == (ssize_t)len) {
+ return stdout;
}
return NULL;
@@ -751,6 +920,7 @@ bug_report_end(FILE *out)
}
}
postscript_dump(out);
+ finish_report(out);
}
#define report_bug(file, line, fmt, ctx) do { \
@@ -763,7 +933,7 @@ bug_report_end(FILE *out)
} while (0) \
#define report_bug_valist(file, line, fmt, ctx, args) do { \
- FILE *out = bug_report_file(file, line); \
+ FILE *out = bug_report_file(file, line); \
if (out) { \
bug_report_begin_valist(out, fmt, args); \
rb_vm_bugreport(ctx, out); \