From 89b3c111ff0b846d9ded0cff3ca08cb566c6cc25 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Aug 2023 04:12:22 +0900 Subject: Redirect to RUBY_BUGREPORT_PATH file --- error.c | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 5 deletions(-) (limited to 'error.c') 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); \ -- cgit v1.2.3