1 /*
2  * Copyright 2018-present MongoDB, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <bson/bson.h>
18 
19 #include "mongoc-error.h"
20 #include "mongoc-error-private.h"
21 #include "mongoc-rpc-private.h"
22 #include "mongoc-client-private.h"
23 
24 bool
mongoc_error_has_label(const bson_t * reply,const char * label)25 mongoc_error_has_label (const bson_t *reply, const char *label)
26 {
27    bson_iter_t iter;
28    bson_iter_t error_labels;
29 
30    BSON_ASSERT (reply);
31    BSON_ASSERT (label);
32 
33    if (bson_iter_init_find (&iter, reply, "errorLabels") &&
34        bson_iter_recurse (&iter, &error_labels)) {
35       while (bson_iter_next (&error_labels)) {
36          if (BSON_ITER_HOLDS_UTF8 (&error_labels) &&
37              !strcmp (bson_iter_utf8 (&error_labels, NULL), label)) {
38             return true;
39          }
40       }
41    }
42 
43    if (!bson_iter_init_find (&iter, reply, "writeConcernError")) {
44       return false;
45    }
46 
47    BSON_ASSERT (bson_iter_recurse (&iter, &iter));
48 
49    if (bson_iter_find (&iter, "errorLabels") &&
50        bson_iter_recurse (&iter, &error_labels)) {
51       while (bson_iter_next (&error_labels)) {
52          if (BSON_ITER_HOLDS_UTF8 (&error_labels) &&
53              !strcmp (bson_iter_utf8 (&error_labels, NULL), label)) {
54             return true;
55          }
56       }
57    }
58 
59    return false;
60 }
61 
62 static bool
_mongoc_error_is_server(bson_error_t * error)63 _mongoc_error_is_server (bson_error_t *error)
64 {
65    if (!error) {
66       return false;
67    }
68 
69    return error->domain == MONGOC_ERROR_SERVER ||
70           error->domain == MONGOC_ERROR_WRITE_CONCERN;
71 }
72 
73 static bool
_mongoc_write_error_is_retryable(bson_error_t * error)74 _mongoc_write_error_is_retryable (bson_error_t *error)
75 {
76    if (!_mongoc_error_is_server (error)) {
77       return false;
78    }
79 
80    switch (error->code) {
81    case MONGOC_SERVER_ERR_HOSTUNREACHABLE:
82    case MONGOC_SERVER_ERR_HOSTNOTFOUND:
83    case MONGOC_SERVER_ERR_NETWORKTIMEOUT:
84    case MONGOC_SERVER_ERR_SHUTDOWNINPROGRESS:
85    case MONGOC_SERVER_ERR_PRIMARYSTEPPEDDOWN:
86    case MONGOC_SERVER_ERR_EXCEEDEDTIMELIMIT:
87    case MONGOC_SERVER_ERR_SOCKETEXCEPTION:
88    case MONGOC_SERVER_ERR_NOTMASTER:
89    case MONGOC_SERVER_ERR_INTERRUPTEDATSHUTDOWN:
90    case MONGOC_SERVER_ERR_INTERRUPTEDDUETOREPLSTATECHANGE:
91    case MONGOC_SERVER_ERR_NOTMASTERNOSLAVEOK:
92    case MONGOC_SERVER_ERR_NOTMASTERORSECONDARY:
93       return true;
94    default:
95       return false;
96    }
97 }
98 
99 static void
_mongoc_write_error_append_retryable_label(bson_t * reply)100 _mongoc_write_error_append_retryable_label (bson_t *reply)
101 {
102    bson_t reply_local = BSON_INITIALIZER;
103 
104    if (!reply) {
105       bson_destroy (&reply_local);
106       return;
107    }
108 
109    bson_copy_to_excluding_noinit (reply, &reply_local, "errorLabels", NULL);
110    _mongoc_error_copy_labels_and_upsert (
111       reply, &reply_local, RETRYABLE_WRITE_ERROR);
112 
113    bson_destroy (reply);
114    bson_steal (reply, &reply_local);
115 }
116 
117 void
_mongoc_write_error_handle_labels(bool cmd_ret,const bson_error_t * cmd_err,bson_t * reply,int32_t server_max_wire_version)118 _mongoc_write_error_handle_labels (bool cmd_ret,
119                                    const bson_error_t *cmd_err,
120                                    bson_t *reply,
121                                    int32_t server_max_wire_version)
122 {
123    bson_error_t error;
124 
125    /* check for a client error. */
126    if (!cmd_ret && _mongoc_error_is_network (cmd_err)) {
127       /* Retryable writes spec: When the driver encounters a network error
128        * communicating with any server version that supports retryable
129        * writes, it MUST add a RetryableWriteError label to that error. */
130       _mongoc_write_error_append_retryable_label (reply);
131       return;
132    }
133 
134    if (server_max_wire_version >= WIRE_VERSION_RETRYABLE_WRITE_ERROR_LABEL) {
135       return;
136    }
137 
138    /* check for a server error. */
139    if (_mongoc_cmd_check_ok_no_wce (
140           reply, MONGOC_ERROR_API_VERSION_2, &error)) {
141       return;
142    }
143 
144    if (_mongoc_write_error_is_retryable (&error)) {
145       _mongoc_write_error_append_retryable_label (reply);
146    }
147 }
148 
149 
150 /*--------------------------------------------------------------------------
151  *
152  * _mongoc_read_error_get_type --
153  *
154  *       Checks if the error or reply from a read command is considered
155  *       retryable according to the retryable reads spec. Checks both
156  *       for a client error (a network exception) and a server error in
157  *       the reply. @cmd_ret and @cmd_err come from the result of a
158  *       read_command function.
159  *
160  *
161  * Return:
162  *       A mongoc_read_error_type_t indicating the type of error (if any).
163  *
164  *--------------------------------------------------------------------------
165  */
166 mongoc_read_err_type_t
_mongoc_read_error_get_type(bool cmd_ret,const bson_error_t * cmd_err,const bson_t * reply)167 _mongoc_read_error_get_type (bool cmd_ret,
168                              const bson_error_t *cmd_err,
169                              const bson_t *reply)
170 {
171    bson_error_t error;
172 
173    /* check for a client error. */
174    if (!cmd_ret && cmd_err && _mongoc_error_is_network (cmd_err)) {
175       /* Retryable reads spec: "considered retryable if [...] any network
176        * exception (e.g. socket timeout or error) */
177       return MONGOC_READ_ERR_RETRY;
178    }
179 
180    /* check for a server error. */
181    if (_mongoc_cmd_check_ok_no_wce (
182           reply, MONGOC_ERROR_API_VERSION_2, &error)) {
183       return MONGOC_READ_ERR_NONE;
184    }
185 
186    switch (error.code) {
187    case MONGOC_SERVER_ERR_INTERRUPTEDATSHUTDOWN:
188    case MONGOC_SERVER_ERR_INTERRUPTEDDUETOREPLSTATECHANGE:
189    case MONGOC_SERVER_ERR_NOTMASTER:
190    case MONGOC_SERVER_ERR_NOTMASTERNOSLAVEOK:
191    case MONGOC_SERVER_ERR_NOTMASTERORSECONDARY:
192    case MONGOC_SERVER_ERR_PRIMARYSTEPPEDDOWN:
193    case MONGOC_SERVER_ERR_SHUTDOWNINPROGRESS:
194    case MONGOC_SERVER_ERR_HOSTNOTFOUND:
195    case MONGOC_SERVER_ERR_HOSTUNREACHABLE:
196    case MONGOC_SERVER_ERR_NETWORKTIMEOUT:
197    case MONGOC_SERVER_ERR_SOCKETEXCEPTION:
198       return MONGOC_READ_ERR_RETRY;
199    default:
200       if (strstr (error.message, "not master") ||
201           strstr (error.message, "node is recovering")) {
202          return MONGOC_READ_ERR_RETRY;
203       }
204       return MONGOC_READ_ERR_OTHER;
205    }
206 }
207 
208 void
_mongoc_error_copy_labels_and_upsert(const bson_t * src,bson_t * dst,char * label)209 _mongoc_error_copy_labels_and_upsert (const bson_t *src,
210                                       bson_t *dst,
211                                       char *label)
212 {
213    bson_iter_t iter;
214    bson_iter_t src_label;
215    bson_t dst_labels;
216    char str[16];
217    uint32_t i = 0;
218    const char *key;
219 
220    BSON_APPEND_ARRAY_BEGIN (dst, "errorLabels", &dst_labels);
221    BSON_APPEND_UTF8 (&dst_labels, "0", label);
222 
223    /* append any other errorLabels already in "src" */
224    if (bson_iter_init_find (&iter, src, "errorLabels") &&
225        bson_iter_recurse (&iter, &src_label)) {
226       while (bson_iter_next (&src_label) && BSON_ITER_HOLDS_UTF8 (&src_label)) {
227          if (strcmp (bson_iter_utf8 (&src_label, NULL), label) != 0) {
228             i++;
229             bson_uint32_to_string (i, &key, str, sizeof str);
230             BSON_APPEND_UTF8 (
231                &dst_labels, key, bson_iter_utf8 (&src_label, NULL));
232          }
233       }
234    }
235 
236    bson_append_array_end (dst, &dst_labels);
237 }
238 
239 /* Defined in SDAM spec under "Application Errors".
240  * @error should have been obtained from a command reply, e.g. with
241  * _mongoc_cmd_check_ok.
242  */
243 bool
_mongoc_error_is_shutdown(bson_error_t * error)244 _mongoc_error_is_shutdown (bson_error_t *error)
245 {
246    if (!_mongoc_error_is_server (error)) {
247       return false;
248    }
249    switch (error->code) {
250    case 11600: /* InterruptedAtShutdown */
251    case 91:    /* ShutdownInProgress */
252       return true;
253    default:
254       return false;
255    }
256 }
257 
258 bool
_mongoc_error_is_not_master(bson_error_t * error)259 _mongoc_error_is_not_master (bson_error_t *error)
260 {
261    if (!_mongoc_error_is_server (error)) {
262       return false;
263    }
264 
265    if (_mongoc_error_is_recovering (error)) {
266       return false;
267    }
268    switch (error->code) {
269    case MONGOC_SERVER_ERR_NOTMASTER:
270    case MONGOC_SERVER_ERR_NOTMASTERNOSLAVEOK:
271       return true;
272    default:
273       return NULL != strstr (error->message, "not master");
274    }
275 }
276 
277 bool
_mongoc_error_is_recovering(bson_error_t * error)278 _mongoc_error_is_recovering (bson_error_t *error)
279 {
280    if (!_mongoc_error_is_server (error)) {
281       return false;
282    }
283    switch (error->code) {
284    case MONGOC_SERVER_ERR_INTERRUPTEDATSHUTDOWN:
285    case MONGOC_SERVER_ERR_INTERRUPTEDDUETOREPLSTATECHANGE:
286    case MONGOC_SERVER_ERR_NOTMASTERORSECONDARY:
287    case MONGOC_SERVER_ERR_PRIMARYSTEPPEDDOWN:
288    case MONGOC_SERVER_ERR_SHUTDOWNINPROGRESS:
289       return true;
290    default:
291       return NULL != strstr (error->message, "not master or secondary") ||
292              NULL != strstr (error->message, "node is recovering");
293    }
294 }
295 
296 /* Assumes @error was parsed as an API V2 error. */
297 bool
_mongoc_error_is_state_change(bson_error_t * error)298 _mongoc_error_is_state_change (bson_error_t *error)
299 {
300    return _mongoc_error_is_recovering (error) ||
301           _mongoc_error_is_not_master (error);
302 }
303 
304 bool
_mongoc_error_is_network(const bson_error_t * error)305 _mongoc_error_is_network (const bson_error_t *error)
306 {
307    if (!error) {
308       return false;
309    }
310    if (error->domain == MONGOC_ERROR_STREAM) {
311       return true;
312    }
313 
314    if (error->domain == MONGOC_ERROR_PROTOCOL &&
315        error->code == MONGOC_ERROR_PROTOCOL_INVALID_REPLY) {
316       return true;
317    }
318 
319    return false;
320 }
321