| /* |
| * |
| * Copyright 2016, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| */ |
| |
| #include "src/core/lib/iomgr/error.h" |
| |
| #include <string.h> |
| |
| #include <grpc/status.h> |
| #include <grpc/support/alloc.h> |
| #include <grpc/support/log.h> |
| #include <grpc/support/string_util.h> |
| #include <grpc/support/useful.h> |
| |
| #ifdef GPR_WINDOWS |
| #include <grpc/support/log_windows.h> |
| #endif |
| |
| #include "src/core/lib/iomgr/error_internal.h" |
| #include "src/core/lib/profiling/timers.h" |
| |
| static void destroy_integer(void *key) {} |
| |
| static void *copy_integer(void *key) { return key; } |
| |
| static long compare_integers(void *key1, void *key2) { |
| return GPR_ICMP((uintptr_t)key1, (uintptr_t)key2); |
| } |
| |
| static void destroy_string(void *str) { gpr_free(str); } |
| |
| static void *copy_string(void *str) { return gpr_strdup(str); } |
| |
| static void destroy_err(void *err) { GRPC_ERROR_UNREF(err); } |
| |
| static void *copy_err(void *err) { return GRPC_ERROR_REF(err); } |
| |
| static void destroy_time(void *tm) { gpr_free(tm); } |
| |
| static gpr_timespec *box_time(gpr_timespec tm) { |
| gpr_timespec *out = gpr_malloc(sizeof(*out)); |
| *out = tm; |
| return out; |
| } |
| |
| static void *copy_time(void *tm) { return box_time(*(gpr_timespec *)tm); } |
| |
| static const gpr_avl_vtable avl_vtable_ints = {destroy_integer, copy_integer, |
| compare_integers, |
| destroy_integer, copy_integer}; |
| |
| static const gpr_avl_vtable avl_vtable_strs = {destroy_integer, copy_integer, |
| compare_integers, destroy_string, |
| copy_string}; |
| |
| static const gpr_avl_vtable avl_vtable_times = { |
| destroy_integer, copy_integer, compare_integers, destroy_time, copy_time}; |
| |
| static const gpr_avl_vtable avl_vtable_errs = { |
| destroy_integer, copy_integer, compare_integers, destroy_err, copy_err}; |
| |
| static const char *error_int_name(grpc_error_ints key) { |
| switch (key) { |
| case GRPC_ERROR_INT_ERRNO: |
| return "errno"; |
| case GRPC_ERROR_INT_FILE_LINE: |
| return "file_line"; |
| case GRPC_ERROR_INT_STREAM_ID: |
| return "stream_id"; |
| case GRPC_ERROR_INT_GRPC_STATUS: |
| return "grpc_status"; |
| case GRPC_ERROR_INT_OFFSET: |
| return "offset"; |
| case GRPC_ERROR_INT_INDEX: |
| return "index"; |
| case GRPC_ERROR_INT_SIZE: |
| return "size"; |
| case GRPC_ERROR_INT_HTTP2_ERROR: |
| return "http2_error"; |
| case GRPC_ERROR_INT_TSI_CODE: |
| return "tsi_code"; |
| case GRPC_ERROR_INT_SECURITY_STATUS: |
| return "security_status"; |
| case GRPC_ERROR_INT_FD: |
| return "fd"; |
| case GRPC_ERROR_INT_WSA_ERROR: |
| return "wsa_error"; |
| case GRPC_ERROR_INT_HTTP_STATUS: |
| return "http_status"; |
| case GRPC_ERROR_INT_LIMIT: |
| return "limit"; |
| case GRPC_ERROR_INT_OCCURRED_DURING_WRITE: |
| return "occurred_during_write"; |
| } |
| GPR_UNREACHABLE_CODE(return "unknown"); |
| } |
| |
| static const char *error_str_name(grpc_error_strs key) { |
| switch (key) { |
| case GRPC_ERROR_STR_KEY: |
| return "key"; |
| case GRPC_ERROR_STR_VALUE: |
| return "value"; |
| case GRPC_ERROR_STR_DESCRIPTION: |
| return "description"; |
| case GRPC_ERROR_STR_OS_ERROR: |
| return "os_error"; |
| case GRPC_ERROR_STR_TARGET_ADDRESS: |
| return "target_address"; |
| case GRPC_ERROR_STR_SYSCALL: |
| return "syscall"; |
| case GRPC_ERROR_STR_FILE: |
| return "file"; |
| case GRPC_ERROR_STR_GRPC_MESSAGE: |
| return "grpc_message"; |
| case GRPC_ERROR_STR_RAW_BYTES: |
| return "raw_bytes"; |
| case GRPC_ERROR_STR_TSI_ERROR: |
| return "tsi_error"; |
| case GRPC_ERROR_STR_FILENAME: |
| return "filename"; |
| case GRPC_ERROR_STR_QUEUED_BUFFERS: |
| return "queued_buffers"; |
| } |
| GPR_UNREACHABLE_CODE(return "unknown"); |
| } |
| |
| static const char *error_time_name(grpc_error_times key) { |
| switch (key) { |
| case GRPC_ERROR_TIME_CREATED: |
| return "created"; |
| } |
| GPR_UNREACHABLE_CODE(return "unknown"); |
| } |
| |
| bool grpc_error_is_special(grpc_error *err) { |
| return err == GRPC_ERROR_NONE || err == GRPC_ERROR_OOM || |
| err == GRPC_ERROR_CANCELLED; |
| } |
| |
| #ifdef GRPC_ERROR_REFCOUNT_DEBUG |
| grpc_error *grpc_error_ref(grpc_error *err, const char *file, int line, |
| const char *func) { |
| if (grpc_error_is_special(err)) return err; |
| gpr_log(GPR_DEBUG, "%p: %" PRIdPTR " -> %" PRIdPTR " [%s:%d %s]", err, |
| err->refs.count, err->refs.count + 1, file, line, func); |
| gpr_ref(&err->refs); |
| return err; |
| } |
| #else |
| grpc_error *grpc_error_ref(grpc_error *err) { |
| if (grpc_error_is_special(err)) return err; |
| gpr_ref(&err->refs); |
| return err; |
| } |
| #endif |
| |
| static void error_destroy(grpc_error *err) { |
| GPR_ASSERT(!grpc_error_is_special(err)); |
| gpr_avl_unref(err->ints); |
| gpr_avl_unref(err->strs); |
| gpr_avl_unref(err->errs); |
| gpr_avl_unref(err->times); |
| gpr_free((void *)gpr_atm_acq_load(&err->error_string)); |
| gpr_free(err); |
| } |
| |
| #ifdef GRPC_ERROR_REFCOUNT_DEBUG |
| void grpc_error_unref(grpc_error *err, const char *file, int line, |
| const char *func) { |
| if (grpc_error_is_special(err)) return; |
| gpr_log(GPR_DEBUG, "%p: %" PRIdPTR " -> %" PRIdPTR " [%s:%d %s]", err, |
| err->refs.count, err->refs.count - 1, file, line, func); |
| if (gpr_unref(&err->refs)) { |
| error_destroy(err); |
| } |
| } |
| #else |
| void grpc_error_unref(grpc_error *err) { |
| if (grpc_error_is_special(err)) return; |
| if (gpr_unref(&err->refs)) { |
| error_destroy(err); |
| } |
| } |
| #endif |
| |
| grpc_error *grpc_error_create(const char *file, int line, const char *desc, |
| grpc_error **referencing, |
| size_t num_referencing) { |
| GPR_TIMER_BEGIN("grpc_error_create", 0); |
| grpc_error *err = gpr_malloc(sizeof(*err)); |
| if (err == NULL) { // TODO(ctiller): make gpr_malloc return NULL |
| return GRPC_ERROR_OOM; |
| } |
| #ifdef GRPC_ERROR_REFCOUNT_DEBUG |
| gpr_log(GPR_DEBUG, "%p create [%s:%d]", err, file, line); |
| #endif |
| err->ints = gpr_avl_add(gpr_avl_create(&avl_vtable_ints), |
| (void *)(uintptr_t)GRPC_ERROR_INT_FILE_LINE, |
| (void *)(uintptr_t)line); |
| err->strs = gpr_avl_add( |
| gpr_avl_add(gpr_avl_create(&avl_vtable_strs), |
| (void *)(uintptr_t)GRPC_ERROR_STR_FILE, gpr_strdup(file)), |
| (void *)(uintptr_t)GRPC_ERROR_STR_DESCRIPTION, gpr_strdup(desc)); |
| err->errs = gpr_avl_create(&avl_vtable_errs); |
| err->next_err = 0; |
| for (size_t i = 0; i < num_referencing; i++) { |
| if (referencing[i] == GRPC_ERROR_NONE) continue; |
| err->errs = gpr_avl_add(err->errs, (void *)(err->next_err++), |
| GRPC_ERROR_REF(referencing[i])); |
| } |
| err->times = gpr_avl_add(gpr_avl_create(&avl_vtable_times), |
| (void *)(uintptr_t)GRPC_ERROR_TIME_CREATED, |
| box_time(gpr_now(GPR_CLOCK_REALTIME))); |
| gpr_atm_no_barrier_store(&err->error_string, 0); |
| gpr_ref_init(&err->refs, 1); |
| GPR_TIMER_END("grpc_error_create", 0); |
| return err; |
| } |
| |
| static grpc_error *copy_error_and_unref(grpc_error *in) { |
| GPR_TIMER_BEGIN("copy_error_and_unref", 0); |
| grpc_error *out; |
| if (grpc_error_is_special(in)) { |
| if (in == GRPC_ERROR_NONE) |
| out = grpc_error_set_int(GRPC_ERROR_CREATE("no error"), |
| GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_OK); |
| else if (in == GRPC_ERROR_OOM) |
| out = GRPC_ERROR_CREATE("oom"); |
| else if (in == GRPC_ERROR_CANCELLED) |
| out = |
| grpc_error_set_int(GRPC_ERROR_CREATE("cancelled"), |
| GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_CANCELLED); |
| else |
| out = GRPC_ERROR_CREATE("unknown"); |
| } else { |
| out = gpr_malloc(sizeof(*out)); |
| #ifdef GRPC_ERROR_REFCOUNT_DEBUG |
| gpr_log(GPR_DEBUG, "%p create copying %p", out, in); |
| #endif |
| out->ints = gpr_avl_ref(in->ints); |
| out->strs = gpr_avl_ref(in->strs); |
| out->errs = gpr_avl_ref(in->errs); |
| out->times = gpr_avl_ref(in->times); |
| gpr_atm_no_barrier_store(&out->error_string, 0); |
| out->next_err = in->next_err; |
| gpr_ref_init(&out->refs, 1); |
| GRPC_ERROR_UNREF(in); |
| } |
| GPR_TIMER_END("copy_error_and_unref", 0); |
| return out; |
| } |
| |
| grpc_error *grpc_error_set_int(grpc_error *src, grpc_error_ints which, |
| intptr_t value) { |
| GPR_TIMER_BEGIN("grpc_error_set_int", 0); |
| grpc_error *new = copy_error_and_unref(src); |
| new->ints = gpr_avl_add(new->ints, (void *)(uintptr_t)which, (void *)value); |
| GPR_TIMER_END("grpc_error_set_int", 0); |
| return new; |
| } |
| |
| typedef struct { |
| grpc_error *error; |
| grpc_status_code code; |
| const char *msg; |
| } special_error_status_map; |
| static special_error_status_map error_status_map[] = { |
| {GRPC_ERROR_NONE, GRPC_STATUS_OK, NULL}, |
| {GRPC_ERROR_CANCELLED, GRPC_STATUS_CANCELLED, "Cancelled"}, |
| {GRPC_ERROR_OOM, GRPC_STATUS_RESOURCE_EXHAUSTED, "Out of memory"}, |
| }; |
| |
| bool grpc_error_get_int(grpc_error *err, grpc_error_ints which, intptr_t *p) { |
| GPR_TIMER_BEGIN("grpc_error_get_int", 0); |
| void *pp; |
| if (grpc_error_is_special(err)) { |
| if (which == GRPC_ERROR_INT_GRPC_STATUS) { |
| for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) { |
| if (error_status_map[i].error == err) { |
| if (p != NULL) *p = error_status_map[i].code; |
| GPR_TIMER_END("grpc_error_get_int", 0); |
| return true; |
| } |
| } |
| } |
| GPR_TIMER_END("grpc_error_get_int", 0); |
| return false; |
| } |
| if (gpr_avl_maybe_get(err->ints, (void *)(uintptr_t)which, &pp)) { |
| if (p != NULL) *p = (intptr_t)pp; |
| GPR_TIMER_END("grpc_error_get_int", 0); |
| return true; |
| } |
| GPR_TIMER_END("grpc_error_get_int", 0); |
| return false; |
| } |
| |
| grpc_error *grpc_error_set_str(grpc_error *src, grpc_error_strs which, |
| const char *value) { |
| GPR_TIMER_BEGIN("grpc_error_set_str", 0); |
| grpc_error *new = copy_error_and_unref(src); |
| new->strs = |
| gpr_avl_add(new->strs, (void *)(uintptr_t)which, gpr_strdup(value)); |
| GPR_TIMER_END("grpc_error_set_str", 0); |
| return new; |
| } |
| |
| const char *grpc_error_get_str(grpc_error *err, grpc_error_strs which) { |
| if (grpc_error_is_special(err)) { |
| if (which == GRPC_ERROR_STR_GRPC_MESSAGE) { |
| for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) { |
| if (error_status_map[i].error == err) { |
| return error_status_map[i].msg; |
| } |
| } |
| } |
| return NULL; |
| } |
| return gpr_avl_get(err->strs, (void *)(uintptr_t)which); |
| } |
| |
| grpc_error *grpc_error_add_child(grpc_error *src, grpc_error *child) { |
| GPR_TIMER_BEGIN("grpc_error_add_child", 0); |
| grpc_error *new = copy_error_and_unref(src); |
| new->errs = gpr_avl_add(new->errs, (void *)(new->next_err++), child); |
| GPR_TIMER_END("grpc_error_add_child", 0); |
| return new; |
| } |
| |
| static const char *no_error_string = "\"No Error\""; |
| static const char *oom_error_string = "\"Out of memory\""; |
| static const char *cancelled_error_string = "\"Cancelled\""; |
| |
| typedef struct { |
| char *key; |
| char *value; |
| } kv_pair; |
| |
| typedef struct { |
| kv_pair *kvs; |
| size_t num_kvs; |
| size_t cap_kvs; |
| } kv_pairs; |
| |
| static void append_kv(kv_pairs *kvs, char *key, char *value) { |
| if (kvs->num_kvs == kvs->cap_kvs) { |
| kvs->cap_kvs = GPR_MAX(3 * kvs->cap_kvs / 2, 4); |
| kvs->kvs = gpr_realloc(kvs->kvs, sizeof(*kvs->kvs) * kvs->cap_kvs); |
| } |
| kvs->kvs[kvs->num_kvs].key = key; |
| kvs->kvs[kvs->num_kvs].value = value; |
| kvs->num_kvs++; |
| } |
| |
| static void collect_kvs(gpr_avl_node *node, char *key(void *k), |
| char *fmt(void *v), kv_pairs *kvs) { |
| if (node == NULL) return; |
| append_kv(kvs, key(node->key), fmt(node->value)); |
| collect_kvs(node->left, key, fmt, kvs); |
| collect_kvs(node->right, key, fmt, kvs); |
| } |
| |
| static char *key_int(void *p) { |
| return gpr_strdup(error_int_name((grpc_error_ints)(uintptr_t)p)); |
| } |
| |
| static char *key_str(void *p) { |
| return gpr_strdup(error_str_name((grpc_error_strs)(uintptr_t)p)); |
| } |
| |
| static char *key_time(void *p) { |
| return gpr_strdup(error_time_name((grpc_error_times)(uintptr_t)p)); |
| } |
| |
| static char *fmt_int(void *p) { |
| char *s; |
| gpr_asprintf(&s, "%" PRIdPTR, (intptr_t)p); |
| return s; |
| } |
| |
| static void append_chr(char c, char **s, size_t *sz, size_t *cap) { |
| if (*sz == *cap) { |
| *cap = GPR_MAX(8, 3 * *cap / 2); |
| *s = gpr_realloc(*s, *cap); |
| } |
| (*s)[(*sz)++] = c; |
| } |
| |
| static void append_str(const char *str, char **s, size_t *sz, size_t *cap) { |
| for (const char *c = str; *c; c++) { |
| append_chr(*c, s, sz, cap); |
| } |
| } |
| |
| static void append_esc_str(const char *str, char **s, size_t *sz, size_t *cap) { |
| static const char *hex = "0123456789abcdef"; |
| append_chr('"', s, sz, cap); |
| for (const uint8_t *c = (const uint8_t *)str; *c; c++) { |
| if (*c < 32 || *c >= 127) { |
| append_chr('\\', s, sz, cap); |
| switch (*c) { |
| case '\b': |
| append_chr('b', s, sz, cap); |
| break; |
| case '\f': |
| append_chr('f', s, sz, cap); |
| break; |
| case '\n': |
| append_chr('n', s, sz, cap); |
| break; |
| case '\r': |
| append_chr('r', s, sz, cap); |
| break; |
| case '\t': |
| append_chr('t', s, sz, cap); |
| break; |
| default: |
| append_chr('u', s, sz, cap); |
| append_chr('0', s, sz, cap); |
| append_chr('0', s, sz, cap); |
| append_chr(hex[*c >> 4], s, sz, cap); |
| append_chr(hex[*c & 0x0f], s, sz, cap); |
| break; |
| } |
| } else { |
| append_chr((char)*c, s, sz, cap); |
| } |
| } |
| append_chr('"', s, sz, cap); |
| } |
| |
| static char *fmt_str(void *p) { |
| char *s = NULL; |
| size_t sz = 0; |
| size_t cap = 0; |
| append_esc_str(p, &s, &sz, &cap); |
| append_chr(0, &s, &sz, &cap); |
| return s; |
| } |
| |
| static char *fmt_time(void *p) { |
| gpr_timespec tm = *(gpr_timespec *)p; |
| char *out; |
| char *pfx = "!!"; |
| switch (tm.clock_type) { |
| case GPR_CLOCK_MONOTONIC: |
| pfx = "@monotonic:"; |
| break; |
| case GPR_CLOCK_REALTIME: |
| pfx = "@"; |
| break; |
| case GPR_CLOCK_PRECISE: |
| pfx = "@precise:"; |
| break; |
| case GPR_TIMESPAN: |
| pfx = ""; |
| break; |
| } |
| gpr_asprintf(&out, "\"%s%" PRId64 ".%09d\"", pfx, tm.tv_sec, tm.tv_nsec); |
| return out; |
| } |
| |
| static void add_errs(gpr_avl_node *n, char **s, size_t *sz, size_t *cap, |
| bool *first) { |
| if (n == NULL) return; |
| add_errs(n->left, s, sz, cap, first); |
| if (!*first) append_chr(',', s, sz, cap); |
| *first = false; |
| const char *e = grpc_error_string(n->value); |
| append_str(e, s, sz, cap); |
| add_errs(n->right, s, sz, cap, first); |
| } |
| |
| static char *errs_string(grpc_error *err) { |
| char *s = NULL; |
| size_t sz = 0; |
| size_t cap = 0; |
| bool first = true; |
| append_chr('[', &s, &sz, &cap); |
| add_errs(err->errs.root, &s, &sz, &cap, &first); |
| append_chr(']', &s, &sz, &cap); |
| append_chr(0, &s, &sz, &cap); |
| return s; |
| } |
| |
| static int cmp_kvs(const void *a, const void *b) { |
| const kv_pair *ka = a; |
| const kv_pair *kb = b; |
| return strcmp(ka->key, kb->key); |
| } |
| |
| static char *finish_kvs(kv_pairs *kvs) { |
| char *s = NULL; |
| size_t sz = 0; |
| size_t cap = 0; |
| |
| append_chr('{', &s, &sz, &cap); |
| for (size_t i = 0; i < kvs->num_kvs; i++) { |
| if (i != 0) append_chr(',', &s, &sz, &cap); |
| append_esc_str(kvs->kvs[i].key, &s, &sz, &cap); |
| gpr_free(kvs->kvs[i].key); |
| append_chr(':', &s, &sz, &cap); |
| append_str(kvs->kvs[i].value, &s, &sz, &cap); |
| gpr_free(kvs->kvs[i].value); |
| } |
| append_chr('}', &s, &sz, &cap); |
| append_chr(0, &s, &sz, &cap); |
| |
| gpr_free(kvs->kvs); |
| return s; |
| } |
| |
| const char *grpc_error_string(grpc_error *err) { |
| GPR_TIMER_BEGIN("grpc_error_string", 0); |
| if (err == GRPC_ERROR_NONE) return no_error_string; |
| if (err == GRPC_ERROR_OOM) return oom_error_string; |
| if (err == GRPC_ERROR_CANCELLED) return cancelled_error_string; |
| |
| void *p = (void *)gpr_atm_acq_load(&err->error_string); |
| if (p != NULL) { |
| GPR_TIMER_END("grpc_error_string", 0); |
| return p; |
| } |
| |
| kv_pairs kvs; |
| memset(&kvs, 0, sizeof(kvs)); |
| |
| collect_kvs(err->ints.root, key_int, fmt_int, &kvs); |
| collect_kvs(err->strs.root, key_str, fmt_str, &kvs); |
| collect_kvs(err->times.root, key_time, fmt_time, &kvs); |
| if (!gpr_avl_is_empty(err->errs)) { |
| append_kv(&kvs, gpr_strdup("referenced_errors"), errs_string(err)); |
| } |
| |
| qsort(kvs.kvs, kvs.num_kvs, sizeof(kv_pair), cmp_kvs); |
| |
| char *out = finish_kvs(&kvs); |
| |
| if (!gpr_atm_rel_cas(&err->error_string, 0, (gpr_atm)out)) { |
| gpr_free(out); |
| out = (char *)gpr_atm_no_barrier_load(&err->error_string); |
| } |
| |
| GPR_TIMER_END("grpc_error_string", 0); |
| return out; |
| } |
| |
| grpc_error *grpc_os_error(const char *file, int line, int err, |
| const char *call_name) { |
| return grpc_error_set_str( |
| grpc_error_set_str( |
| grpc_error_set_int(grpc_error_create(file, line, "OS Error", NULL, 0), |
| GRPC_ERROR_INT_ERRNO, err), |
| GRPC_ERROR_STR_OS_ERROR, strerror(err)), |
| GRPC_ERROR_STR_SYSCALL, call_name); |
| } |
| |
| #ifdef GPR_WINDOWS |
| grpc_error *grpc_wsa_error(const char *file, int line, int err, |
| const char *call_name) { |
| char *utf8_message = gpr_format_message(err); |
| grpc_error *error = grpc_error_set_str( |
| grpc_error_set_str( |
| grpc_error_set_int(grpc_error_create(file, line, "OS Error", NULL, 0), |
| GRPC_ERROR_INT_WSA_ERROR, err), |
| GRPC_ERROR_STR_OS_ERROR, utf8_message), |
| GRPC_ERROR_STR_SYSCALL, call_name); |
| gpr_free(utf8_message); |
| return error; |
| } |
| #endif |
| |
| bool grpc_log_if_error(const char *what, grpc_error *error, const char *file, |
| int line) { |
| if (error == GRPC_ERROR_NONE) return true; |
| const char *msg = grpc_error_string(error); |
| gpr_log(file, line, GPR_LOG_SEVERITY_ERROR, "%s: %s", what, msg); |
| GRPC_ERROR_UNREF(error); |
| return false; |
| } |