xref: /6.0.3/kv_engine/daemon/subdocument.cc (revision b6f27f57)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2015 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18#include "subdocument.h"
19
20#include "buckets.h"
21#include "connections.h"
22#include "debug_helpers.h"
23#include "mcaudit.h"
24#include "mcbp.h"
25#include "protocol/mcbp/engine_wrapper.h"
26#include "subdoc/util.h"
27#include "subdocument_context.h"
28#include "subdocument_traits.h"
29#include "subdocument_validators.h"
30#include "timings.h"
31#include "topkeys.h"
32#include "utilities/logtags.h"
33#include "utilities/protocol2text.h"
34#include "xattr/key_validator.h"
35#include "xattr/utils.h"
36
37#include <memcached/protocol_binary.h>
38#include <memcached/types.h>
39#include <platform/histogram.h>
40#include <xattr/blob.h>
41#include <gsl/gsl>
42
43static const std::array<SubdocCmdContext::Phase, 2> phases{{SubdocCmdContext::Phase::XATTR,
44                                                            SubdocCmdContext::Phase::Body}};
45
46using namespace mcbp::subdoc;
47
48/******************************************************************************
49 * Subdocument executors
50 *****************************************************************************/
51
52/*
53 * Declarations
54 */
55static bool subdoc_fetch(Cookie& cookie,
56                         SubdocCmdContext& ctx,
57                         ENGINE_ERROR_CODE ret,
58                         const char* key,
59                         size_t keylen,
60                         uint16_t vbucket,
61                         uint64_t cas);
62
63static bool subdoc_operate(SubdocCmdContext& context);
64
65static ENGINE_ERROR_CODE subdoc_update(SubdocCmdContext& context,
66                                       ENGINE_ERROR_CODE ret,
67                                       const char* key, size_t keylen,
68                                       uint16_t vbucket, uint32_t expiration);
69static void subdoc_response(Cookie& cookie, SubdocCmdContext& context);
70
71// Debug - print details of the specified subdocument command.
72static void subdoc_print_command(Connection& c,
73                                 protocol_binary_command cmd,
74                                 const char* key,
75                                 const uint16_t keylen,
76                                 const char* path,
77                                 const size_t pathlen,
78                                 const char* value,
79                                 const size_t vallen) {
80    char clean_key[KEY_MAX_LENGTH + 32];
81    char clean_path[SUBDOC_PATH_MAX_LENGTH];
82    char clean_value[80]; // only print the first few characters of the value.
83    if ((key_to_printable_buffer(clean_key, sizeof(clean_key), c.getId(), true,
84                                 memcached_opcode_2_text(cmd), key, keylen)
85                    != -1)
86                    && (buf_to_printable_buffer(clean_path, sizeof(clean_path),
87                                                path, pathlen) != -1)) {
88        // print key, path & value if there is a value.
89        if ((vallen > 0)
90                        && (buf_to_printable_buffer(clean_value,
91                                                    sizeof(clean_value), value,
92                                                    vallen) != -1)) {
93            LOG_DEBUG("{} path:'{}' value:'{}'",
94                      cb::logtags::tagUserData(clean_key),
95                      cb::logtags::tagUserData(clean_path),
96                      cb::logtags::tagUserData(clean_value));
97
98        } else {
99            // key & path only
100            LOG_DEBUG("{} path:'{}'",
101                      cb::logtags::tagUserData(clean_key),
102                      cb::logtags::tagUserData(clean_path));
103        }
104    }
105}
106
107/*
108 * Definitions
109 */
110static void create_single_path_context(SubdocCmdContext& context,
111                                       Cookie& cookie,
112                                       const SubdocCmdTraits traits,
113                                       const void* packet,
114                                       cb::const_char_buffer value,
115                                       doc_flag doc_flags) {
116    const protocol_binary_request_subdocument *req =
117        reinterpret_cast<const protocol_binary_request_subdocument*>(packet);
118
119    auto flags = static_cast<protocol_binary_subdoc_flag>(
120            req->message.extras.subdoc_flags);
121    const protocol_binary_command mcbp_cmd =
122        protocol_binary_command(req->message.header.request.opcode);
123    const uint16_t pathlen = ntohs(req->message.extras.pathlen);
124
125    // Path is the first thing in the value; remainder is the operation
126    // value.
127    auto path = value;
128    path.len = pathlen;
129
130    const bool xattr = (flags & SUBDOC_FLAG_XATTR_PATH);
131    const SubdocCmdContext::Phase phase = xattr ?
132                                          SubdocCmdContext::Phase::XATTR :
133                                          SubdocCmdContext::Phase::Body;
134    auto& ops = context.getOperations(phase);
135
136    if (xattr) {
137        size_t xattr_keylen;
138        is_valid_xattr_key({path.data(), path.size()}, xattr_keylen);
139        context.set_xattr_key({path.data(), xattr_keylen});
140    }
141
142    if (flags & SUBDOC_FLAG_EXPAND_MACROS) {
143        context.do_macro_expansion = true;
144    }
145
146    if (hasAccessDeleted(doc_flags)) {
147        context.do_allow_deleted_docs = true;
148    }
149
150    // If Mkdoc or Add is specified, this implies MKDIR_P, ensure that it's set
151    // here
152    if (impliesMkdir_p(doc_flags)) {
153        flags = flags | SUBDOC_FLAG_MKDIR_P;
154    }
155
156    context.setMutationSemantics(doc_flags);
157
158    // Decode as single path; add a single operation to the context.
159    if (traits.request_has_value) {
160        // Adjust value to move past the path.
161        value.buf += pathlen;
162        value.len -= pathlen;
163
164        ops.emplace_back(SubdocCmdContext::OperationSpec{traits, flags,
165                                                         path, value});
166    } else {
167        ops.emplace_back(SubdocCmdContext::OperationSpec{traits, flags, path});
168    }
169
170    if (impliesMkdir_p(doc_flags)) {
171        context.jroot_type = Subdoc::Util::get_root_type(
172                traits.subdocCommand, path.buf, path.len);
173    }
174
175    if (settings.getVerbose() > 1) {
176        const uint8_t extlen = req->message.header.request.extlen;
177        const char* key = (char*)packet + sizeof(req->message.header) + extlen;
178        const uint16_t keylen = ntohs(req->message.header.request.keylen);
179
180        subdoc_print_command(cookie.getConnection(),
181                             mcbp_cmd,
182                             key,
183                             keylen,
184                             path.buf,
185                             path.len,
186                             value.buf,
187                             value.len);
188    }
189}
190
191static void create_multi_path_context(SubdocCmdContext& context,
192                                      Cookie& cookie,
193                                      const SubdocCmdTraits traits,
194                                      const void* packet,
195                                      cb::const_char_buffer value,
196                                      doc_flag doc_flags) {
197    // Decode each of lookup specs from the value into our command context.
198    context.setMutationSemantics(doc_flags);
199    size_t offset = 0;
200    while (offset < value.len) {
201        protocol_binary_command binprot_cmd;
202        protocol_binary_subdoc_flag flags;
203        size_t headerlen;
204        cb::const_char_buffer path;
205        cb::const_char_buffer spec_value;
206        if (traits.is_mutator) {
207            auto* spec = reinterpret_cast<const protocol_binary_subdoc_multi_mutation_spec*>
208                (value.buf + offset);
209            headerlen = sizeof(*spec);
210            binprot_cmd = protocol_binary_command(spec->opcode);
211            flags = protocol_binary_subdoc_flag(spec->flags);
212            path = {value.buf + offset + headerlen,
213                    htons(spec->pathlen)};
214            spec_value = {value.buf + offset + headerlen + path.len,
215                          htonl(spec->valuelen)};
216
217        } else {
218            auto* spec = reinterpret_cast<const protocol_binary_subdoc_multi_lookup_spec*>
219                (value.buf + offset);
220            headerlen = sizeof(*spec);
221            binprot_cmd = protocol_binary_command(spec->opcode);
222            flags = protocol_binary_subdoc_flag(spec->flags);
223            path = {value.buf + offset + headerlen,
224                    htons(spec->pathlen)};
225            spec_value = {nullptr, 0};
226        }
227
228        auto traits = get_subdoc_cmd_traits(binprot_cmd);
229        if (impliesMkdir_p(doc_flags) &&
230            context.jroot_type == 0) {
231            // Determine the root type
232            context.jroot_type = Subdoc::Util::get_root_type(
233                    traits.subdocCommand, path.buf, path.len);
234        }
235
236        if (flags & SUBDOC_FLAG_EXPAND_MACROS) {
237            context.do_macro_expansion = true;
238        }
239
240        if (hasAccessDeleted(doc_flags)) {
241            context.do_allow_deleted_docs = true;
242        }
243
244        const bool xattr = (flags & SUBDOC_FLAG_XATTR_PATH);
245        if (xattr) {
246            size_t xattr_keylen;
247            is_valid_xattr_key({path.data(), path.size()}, xattr_keylen);
248            context.set_xattr_key({path.data(), xattr_keylen});
249        }
250
251        const SubdocCmdContext::Phase phase = xattr ?
252                                              SubdocCmdContext::Phase::XATTR :
253                                              SubdocCmdContext::Phase::Body;
254
255        auto& ops = context.getOperations(phase);
256
257        // Mkdoc and Add imply MKDIR_P, ensure that MKDIR_P is set
258        if (impliesMkdir_p(doc_flags)) {
259            flags = flags | SUBDOC_FLAG_MKDIR_P;
260        }
261        if (traits.mcbpCommand == PROTOCOL_BINARY_CMD_DELETE) {
262            context.do_delete_doc = true;
263        }
264        ops.emplace_back(SubdocCmdContext::OperationSpec{traits, flags, path,
265                                                         spec_value});
266        offset += headerlen + path.len + spec_value.len;
267    }
268
269    if (settings.getVerbose() > 1) {
270        const protocol_binary_request_subdocument *req =
271            reinterpret_cast<const protocol_binary_request_subdocument*>(packet);
272
273        const protocol_binary_command mcbp_cmd =
274            protocol_binary_command(req->message.header.request.opcode);
275
276        const uint8_t extlen = req->message.header.request.extlen;
277        const char* key = (char*)packet + sizeof(req->message.header) + extlen;
278        const uint16_t keylen = ntohs(req->message.header.request.keylen);
279
280        const char path[] = "<multipath>";
281        subdoc_print_command(cookie.getConnection(),
282                             mcbp_cmd,
283                             key,
284                             keylen,
285                             path,
286                             strlen(path),
287                             value.buf,
288                             value.len);
289    }
290}
291
292static SubdocCmdContext* subdoc_create_context(Cookie& cookie,
293                                               const SubdocCmdTraits traits,
294                                               const void* packet,
295                                               cb::const_char_buffer value,
296                                               doc_flag doc_flags) {
297    try {
298        std::unique_ptr<SubdocCmdContext> context;
299        context.reset(new SubdocCmdContext(cookie, traits));
300        switch (traits.path) {
301        case SubdocPath::SINGLE:
302            create_single_path_context(
303                    *context.get(), cookie, traits, packet, value, doc_flags);
304            break;
305
306        case SubdocPath::MULTI:
307            create_multi_path_context(
308                    *context.get(), cookie, traits, packet, value, doc_flags);
309            break;
310        }
311
312        return context.release();
313    } catch (const std::bad_alloc&) {
314        return nullptr;
315    }
316}
317
318/* Decode the specified expiration value for the specified request.
319 */
320uint32_t subdoc_decode_expiration(const protocol_binary_request_header* header,
321                                  const SubdocCmdTraits traits) {
322    // Expiration is zero (never expire) unless an (optional) 4-byte expiry
323    // value was included in the extras.
324    const char* expiration_ptr = nullptr;
325
326    // Single-path and multi-path have different extras encodings:
327    switch (traits.path) {
328    case SubdocPath::SINGLE:
329        if ((header->request.extlen == SUBDOC_EXPIRY_EXTRAS_LEN) ||
330            (header->request.extlen == SUBDOC_ALL_EXTRAS_LEN)) {
331            expiration_ptr = reinterpret_cast<const char*>(header) +
332                             sizeof(*header) + SUBDOC_BASIC_EXTRAS_LEN;
333        }
334        break;
335
336    case SubdocPath::MULTI:
337        if ((header->request.extlen == SUBDOC_MULTI_EXPIRY_EXTRAS_LEN) ||
338            (header->request.extlen == SUBDOC_MULTI_ALL_EXTRAS_LEN)) {
339            expiration_ptr = reinterpret_cast<const char*>(header) +
340                             sizeof(*header);
341        }
342        break;
343    }
344
345    if (expiration_ptr != nullptr) {
346        return ntohl(*reinterpret_cast<const uint32_t*>(expiration_ptr));
347    } else {
348        return 0;
349    }
350}
351
352/**
353 * Main function which handles execution of all sub-document
354 * commands: fetches, operates on, updates and finally responds to the client.
355 *
356 * Invoked via extern "C" trampoline functions (see later) which populate the
357 * subdocument elements of executors[].
358 *
359 * @param cookie the command context
360 * @param traits Traits associated with the specific command.
361 */
362static void subdoc_executor(Cookie& cookie, const SubdocCmdTraits traits) {
363    // 0. Parse the request and log it if debug enabled.
364    auto packet = cookie.getPacket(Cookie::PacketContent::Full);
365    const auto* req =
366            reinterpret_cast<const protocol_binary_request_subdocument*>(
367                    packet.data());
368    const protocol_binary_request_header* header = &req->message.header;
369
370    const uint8_t extlen = header->request.extlen;
371    const uint16_t keylen = ntohs(header->request.keylen);
372    const uint32_t bodylen = ntohl(header->request.bodylen);
373    const uint16_t vbucket = ntohs(header->request.vbucket);
374    const uint64_t cas = ntohll(header->request.cas);
375
376    const char* key = (char*)packet.data() + sizeof(*header) + extlen;
377
378    const char* value = key + keylen;
379    const uint32_t vallen = bodylen - keylen - extlen;
380
381    const uint32_t expiration = subdoc_decode_expiration(header, traits);
382    const doc_flag doc_flags = subdoc_decode_doc_flags(header, traits.path);
383
384    // We potentially need to make multiple attempts at this as the engine may
385    // return EWOULDBLOCK if not initially resident, hence initialise ret to
386    // c->aiostat.
387    auto ret = cookie.swapAiostat(ENGINE_SUCCESS);
388
389    // If client didn't specify a CAS, we still use CAS internally to check
390    // that we are updating the same version of the document as was fetched.
391    // However in this case we auto-retry in the event of a concurrent update
392    // by some other client.
393    const bool auto_retry = (cas == 0);
394
395    // We specify a finite number of times to retry; to prevent the (extremely
396    // unlikely) event that we are fighting with another client for the
397    // correct CAS value for an arbitrary amount of time (and to defend against
398    // possible bugs in our code ;)
399    const int MAXIMUM_ATTEMPTS = 100;
400
401    cookie.logCommand();
402
403    int attempts = 0;
404    do {
405        attempts++;
406
407        // 0. If we don't already have a command context, allocate one
408        // (we may already have one if this is an auto_retry or a re-execution
409        // due to EWOULDBLOCK).
410        auto* context =
411                dynamic_cast<SubdocCmdContext*>(cookie.getCommandContext());
412        if (context == nullptr) {
413            cb::const_char_buffer value_buf{value, vallen};
414            context = subdoc_create_context(
415                    cookie, traits, packet.data(), value_buf, doc_flags);
416            if (context == nullptr) {
417                cookie.sendResponse(cb::mcbp::Status::Enomem);
418                return;
419            }
420            cookie.setCommandContext(context);
421        }
422
423        // 1. Attempt to fetch from the engine the document to operate on. Only
424        // continue if it returned true, otherwise return from this function
425        // (which may result in it being called again later in the EWOULDBLOCK
426        // case).
427        if (!subdoc_fetch(cookie, *context, ret, key, keylen, vbucket, cas)) {
428            return;
429        }
430
431        // 2. Perform the operation specified by CMD. Again, return if it fails.
432        if (!subdoc_operate(*context)) {
433            return;
434        }
435
436        // 3. Update the document in the engine (mutations only).
437        ret = subdoc_update(*context, ret, key, keylen, vbucket, expiration);
438        if (ret == ENGINE_KEY_EEXISTS) {
439            if (auto_retry) {
440                // Retry the operation. Reset the command context and related
441                // state, so start from the beginning again.
442                ret = ENGINE_SUCCESS;
443
444                cookie.setCommandContext();
445                continue;
446            } else {
447                // No auto-retry - return status back to client and return.
448                cookie.sendResponse(cb::engine_errc(ret));
449                return;
450            }
451        } else if (ret != ENGINE_SUCCESS) {
452            return;
453        }
454
455        // 4. Form a response and send it back to the client.
456        subdoc_response(cookie, *context);
457
458        // Update stats. Treat all mutations as 'cmd_set', all accesses as 'cmd_get',
459        // in addition to specific subdoc counters. (This is mainly so we
460        // see subdoc commands in the GUI, which used cmd_set / cmd_get).
461        auto* thread_stats = get_thread_stats(&cookie.getConnection());
462        if (context->traits.is_mutator) {
463            thread_stats->cmd_subdoc_mutation++;
464            thread_stats->bytes_subdoc_mutation_total += context->out_doc_len;
465            thread_stats->bytes_subdoc_mutation_inserted +=
466                    context->getOperationValueBytesTotal();
467
468            SLAB_INCR(&cookie.getConnection(), cmd_set);
469        } else {
470            thread_stats->cmd_subdoc_lookup++;
471            thread_stats->bytes_subdoc_lookup_total += context->in_doc.len;
472            thread_stats->bytes_subdoc_lookup_extracted += context->response_val_len;
473
474            STATS_HIT(&cookie.getConnection(), get);
475        }
476        update_topkeys(cookie);
477        return;
478    } while (auto_retry && attempts < MAXIMUM_ATTEMPTS);
479
480    // Hit maximum attempts - this theoretically could happen but shouldn't
481    // in reality.
482    const auto mcbp_cmd = protocol_binary_command(header->request.opcode);
483
484    auto& c = cookie.getConnection();
485    LOG_WARNING(
486            "{}: Subdoc: Hit maximum number of auto-retry attempts ({}) when "
487            "attempting to perform op {} for client {} - returning TMPFAIL",
488            c.getId(),
489            MAXIMUM_ATTEMPTS,
490            memcached_opcode_2_text(mcbp_cmd),
491            c.getDescription());
492    cookie.sendResponse(cb::mcbp::Status::Etmpfail);
493}
494
495// Fetch the item to operate on from the engine.
496// Returns true if the command was successful (and execution should continue),
497// else false.
498static bool subdoc_fetch(Cookie& cookie,
499                         SubdocCmdContext& ctx,
500                         ENGINE_ERROR_CODE ret,
501                         const char* key,
502                         size_t keylen,
503                         uint16_t vbucket,
504                         uint64_t cas) {
505    if (!ctx.fetchedItem && !ctx.needs_new_doc) {
506        if (ret == ENGINE_SUCCESS) {
507            DocKey get_key(reinterpret_cast<const uint8_t*>(key),
508                           keylen,
509                           cookie.getConnection().getDocNamespace());
510            DocStateFilter state = DocStateFilter::Alive;
511            if (ctx.do_allow_deleted_docs) {
512                state = DocStateFilter::AliveOrDeleted;
513            }
514            auto r = bucket_get(cookie, get_key, vbucket, state);
515            if (r.first == cb::engine_errc::success) {
516                ctx.fetchedItem = std::move(r.second);
517                ret = ENGINE_SUCCESS;
518            } else {
519                ret = ENGINE_ERROR_CODE(r.first);
520                ret = ctx.connection.remapErrorCode(ret);
521            }
522        }
523
524        switch (ret) {
525        case ENGINE_SUCCESS:
526            if (ctx.traits.is_mutator &&
527                ctx.mutationSemantics == MutationSemantics::Add) {
528                cookie.sendResponse(cb::mcbp::Status::KeyEexists);
529                return false;
530            }
531            ctx.needs_new_doc = false;
532            break;
533
534        case ENGINE_KEY_ENOENT:
535            if (ctx.traits.is_mutator &&
536                ctx.mutationSemantics == MutationSemantics::Replace) {
537                cookie.sendResponse(cb::engine_errc(ret));
538                return false;
539            }
540
541            // The item does not exist. Check the current command context to
542            // determine if we should at all write a new document (i.e. pretend
543            // it exists) and defer insert until later.. OR if we should simply
544            // bail.
545
546            if (ctx.jroot_type == JSONSL_T_LIST) {
547                ctx.in_doc = {"[]", 2};
548            } else if (ctx.jroot_type == JSONSL_T_OBJECT) {
549                ctx.in_doc = {"{}", 2};
550            } else {
551                cookie.sendResponse(cb::engine_errc(ret));
552                return false;
553            }
554
555            // Indicate that a new document is required:
556            ctx.needs_new_doc = true;
557            ctx.in_datatype = PROTOCOL_BINARY_DATATYPE_JSON;
558            return true;
559
560        case ENGINE_EWOULDBLOCK:
561            cookie.setEwouldblock(true);
562            return false;
563
564        case ENGINE_DISCONNECT:
565            cookie.getConnection().setState(McbpStateMachine::State::closing);
566            return false;
567
568        default:
569            cookie.sendResponse(cb::engine_errc(ret));
570            return false;
571        }
572    }
573
574    if (ctx.in_doc.buf == nullptr) {
575        // Retrieve the item_info the engine, and if necessary
576        // uncompress it so subjson can parse it.
577        auto status = ctx.get_document_for_searching(cas);
578
579        if (status != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
580            // Failed. Note c.item and c.commandContext will both be freed for
581            // us as part of preparing for the next command.
582            cookie.sendResponse(cb::mcbp::Status(status));
583            return false;
584        }
585    }
586
587    return true;
588}
589
590/**
591 * Perform the subjson operation specified by {spec} to one path in the
592 * document.
593 */
594static protocol_binary_response_status
595subdoc_operate_one_path(SubdocCmdContext& context, SubdocCmdContext::OperationSpec& spec,
596                        const cb::const_char_buffer& in_doc) {
597
598    // Prepare the specified sub-document command.
599    auto& op = context.connection.getThread()->subdoc_op;
600    op.clear();
601    op.set_result_buf(&spec.result);
602    op.set_code(spec.traits.subdocCommand);
603    op.set_doc(in_doc.buf, in_doc.len);
604
605    if (spec.flags & SUBDOC_FLAG_EXPAND_MACROS) {
606        auto padded_macro = context.get_padded_macro(spec.value);
607        op.set_value(padded_macro.buf, padded_macro.len);
608    } else {
609        op.set_value(spec.value.buf, spec.value.len);
610    }
611
612    if (context.getCurrentPhase() == SubdocCmdContext::Phase::XATTR &&
613        spec.path.buf[0] == '$') {
614        if (spec.path.buf[1] == 'd') {
615            // This is a call to the "$document" (the validator stopped all of
616            // the other ones), so replace the document with
617            // the document virtual one..
618            auto doc = context.get_document_vattr();
619            op.set_doc(doc.data(), doc.size());
620        } else if (spec.path.buf[1] == 'X') {
621            auto doc = context.get_xtoc_vattr();
622            op.set_doc(doc.data(), doc.size());
623        }
624    }
625
626    // ... and execute it.
627    const auto subdoc_res = op.op_exec(spec.path.buf, spec.path.len);
628
629    switch (subdoc_res) {
630    case Subdoc::Error::SUCCESS:
631        return PROTOCOL_BINARY_RESPONSE_SUCCESS;
632
633    case Subdoc::Error::PATH_ENOENT:
634        return PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_ENOENT;
635
636    case Subdoc::Error::PATH_MISMATCH:
637        return PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_MISMATCH;
638
639    case Subdoc::Error::DOC_ETOODEEP:
640        return PROTOCOL_BINARY_RESPONSE_SUBDOC_DOC_E2DEEP;
641
642    case Subdoc::Error::PATH_EINVAL:
643        return PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_EINVAL;
644
645    case Subdoc::Error::DOC_NOTJSON:
646        return PROTOCOL_BINARY_RESPONSE_SUBDOC_DOC_NOTJSON;
647
648    case Subdoc::Error::DOC_EEXISTS:
649        return PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_EEXISTS;
650
651    case Subdoc::Error::PATH_E2BIG:
652        return PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_E2BIG;
653
654    case Subdoc::Error::NUM_E2BIG:
655        return PROTOCOL_BINARY_RESPONSE_SUBDOC_NUM_ERANGE;
656
657    case Subdoc::Error::DELTA_EINVAL:
658        return PROTOCOL_BINARY_RESPONSE_SUBDOC_DELTA_EINVAL;
659
660    case Subdoc::Error::VALUE_CANTINSERT:
661        return PROTOCOL_BINARY_RESPONSE_SUBDOC_VALUE_CANTINSERT;
662
663    case Subdoc::Error::DELTA_OVERFLOW:
664        return PROTOCOL_BINARY_RESPONSE_SUBDOC_VALUE_CANTINSERT;
665
666    case Subdoc::Error::VALUE_ETOODEEP:
667        return PROTOCOL_BINARY_RESPONSE_SUBDOC_VALUE_ETOODEEP;
668
669    default:
670        // TODO: handle remaining errors.
671        LOG_DEBUG("Unexpected response from subdoc: {} ({:x})",
672                  subdoc_res,
673                  subdoc_res);
674        return PROTOCOL_BINARY_RESPONSE_EINTERNAL;
675    }
676}
677
678/**
679 * Perform the wholedoc (mcbp) operation defined by spec
680 */
681static protocol_binary_response_status subdoc_operate_wholedoc(
682        SubdocCmdContext& context,
683        SubdocCmdContext::OperationSpec& spec,
684        cb::const_char_buffer& doc) {
685    switch (spec.traits.mcbpCommand) {
686    case PROTOCOL_BINARY_CMD_GET:
687        if (doc.size() == 0) {
688            // Size of zero indicates the document body ("path") doesn't exist.
689            return PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_ENOENT;
690        }
691        spec.result.set_matchloc({doc.buf, doc.len});
692        return PROTOCOL_BINARY_RESPONSE_SUCCESS;
693
694    case PROTOCOL_BINARY_CMD_SET:
695        spec.result.push_newdoc({spec.value.buf, spec.value.len});
696        return PROTOCOL_BINARY_RESPONSE_SUCCESS;
697
698    case PROTOCOL_BINARY_CMD_DELETE:
699        context.in_datatype &= ~BODY_ONLY_DATATYPE_MASK;
700        spec.result.push_newdoc({nullptr, 0});
701        return PROTOCOL_BINARY_RESPONSE_SUCCESS;
702
703    default:
704        return PROTOCOL_BINARY_RESPONSE_EINVAL;
705    }
706}
707
708/**
709 * Run through all of the subdoc operations for the current phase on
710 * a single 'document' (either the user document, or a XATTR).
711 *
712 * @param context The context object for this operation
713 * @param doc the document to operate on
714 * @param doc_datatype The datatype of the document. Updated if a
715 *                     wholedoc op changes the datatype.
716 * @param temp_buffer where to store the data for our temporary buffer
717 *                    allocations if we need to change the doc.
718 * @param modified set to true upon return if any modifications happened
719 *                 to the input document.
720 * @return true if we should continue processing this request,
721 *         false if we've sent the error packet and should temrinate
722 *               execution for this request
723 *
724 * @throws std::bad_alloc if allocation fails
725 */
726static bool operate_single_doc(SubdocCmdContext& context,
727                               cb::const_char_buffer& doc,
728                               protocol_binary_datatype_t& doc_datatype,
729                               std::unique_ptr<char[]>& temp_buffer,
730                               bool& modified) {
731    modified = false;
732    auto& operations = context.getOperations();
733
734    // 2. Perform each of the operations on document.
735    for (auto op = operations.begin(); op != operations.end(); op++) {
736        switch (op->traits.scope) {
737        case CommandScope::SubJSON:
738            if (mcbp::datatype::is_json(doc_datatype)) {
739                // Got JSON, perform the operation.
740                op->status = subdoc_operate_one_path(context, *op, doc);
741            } else {
742                // No good; need to have JSON.
743                op->status = PROTOCOL_BINARY_RESPONSE_SUBDOC_DOC_NOTJSON;
744            }
745            break;
746
747        case CommandScope::WholeDoc:
748            op->status = subdoc_operate_wholedoc(context, *op, doc);
749        }
750
751        if (op->status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
752            if (context.traits.is_mutator) {
753                modified = true;
754
755                // Determine how much space we now need.
756                size_t new_doc_len = 0;
757                for (auto& loc : op->result.newdoc()) {
758                    new_doc_len += loc.length;
759                }
760
761                // TODO-PERF: We need to create a contiguous input
762                // region for the next subjson call, from the set of
763                // iovecs in the result. We can't simply write into
764                // the dynamic_buffer, as that may be the underlying
765                // storage for iovecs from the result. Ideally we'd
766                // either permit subjson to take an iovec as input, or
767                // permit subjson to take all the multipaths at once.
768                // For now we make a contiguous region in a temporary
769                // std::vector, and point in_doc at that.
770
771                // Allocate an extra byte to make sure we can zero term it
772                // (in case we want to use cJSON_Parse() ;-)
773                std::unique_ptr<char[]> temp(new char[new_doc_len + 1]);
774                temp[new_doc_len] = '\0';
775
776                size_t offset = 0;
777                for (auto& loc : op->result.newdoc()) {
778                    std::copy(loc.at, loc.at + loc.length, temp.get() + offset);
779                    offset += loc.length;
780                }
781
782                // Copying complete - safe to delete the old temp_doc
783                // (even if it was the source of some of the newdoc
784                // iovecs).
785                temp_buffer.swap(temp);
786                doc.buf = temp_buffer.get();
787                doc.len = new_doc_len;
788
789                if (op->traits.scope == CommandScope::WholeDoc) {
790                    // the entire document has been replaced as part of a
791                    // wholedoc op update the datatype to match
792                    JSON_checker::Validator validator;
793                    bool isValidJson = validator.validate(
794                            reinterpret_cast<const uint8_t*>(doc.data()),
795                            doc.size());
796
797                    // don't alter context.in_datatype directly here in case we
798                    // are in xattrs phase
799                    if (isValidJson) {
800                        doc_datatype |= PROTOCOL_BINARY_DATATYPE_JSON;
801                    } else {
802                        doc_datatype &= ~PROTOCOL_BINARY_DATATYPE_JSON;
803                    }
804                }
805            } else { // lookup
806                // nothing to do.
807            }
808        } else {
809            switch (context.traits.path) {
810            case SubdocPath::SINGLE:
811                // Failure of a (the only) op stops execution and returns an
812                // error to the client.
813                context.cookie.sendResponse(cb::mcbp::Status(op->status));
814                return false;
815
816            case SubdocPath::MULTI:
817                context.overall_status
818                    = PROTOCOL_BINARY_RESPONSE_SUBDOC_MULTI_PATH_FAILURE;
819                if (context.traits.is_mutator) {
820                    // For mutations, this stops the operation - however as
821                    // we need to respond with a body indicating the index
822                    // which failed we return true indicating 'success'.
823                    return true;
824                } else {
825                    // For lookup; an operation failing doesn't stop us
826                    // continuing with the rest of the operations
827                    // - continue with the next operation.
828                    continue;
829                }
830
831                break;
832            }
833        }
834    }
835
836    return true;
837}
838
839static ENGINE_ERROR_CODE validate_vattr_privilege(SubdocCmdContext& context) {
840    auto key = context.get_xattr_key();
841
842    // The $document vattr doesn't require any xattr permissions.
843
844    if (key.buf[1] == 'X') {
845        // In the xtoc case we want to see which privileges the connection has
846        // to determine which XATTRs we tell the user about
847
848        bool xattrRead = false;
849        auto access = context.connection.checkPrivilege(
850                cb::rbac::Privilege::XattrRead, context.cookie);
851        switch (access) {
852        case cb::rbac::PrivilegeAccess::Ok:
853            xattrRead = true;
854            break;
855        case cb::rbac::PrivilegeAccess::Fail:
856            xattrRead = false;
857            break;
858        case cb::rbac::PrivilegeAccess::Stale:
859            return ENGINE_AUTH_STALE;
860        }
861
862        bool xattrSysRead = false;
863        access = context.connection.checkPrivilege(
864                cb::rbac::Privilege::SystemXattrRead, context.cookie);
865        switch (access) {
866        case cb::rbac::PrivilegeAccess::Ok:
867            xattrSysRead = true;
868            break;
869        case cb::rbac::PrivilegeAccess::Fail:
870            xattrSysRead = false;
871            break;
872        case cb::rbac::PrivilegeAccess::Stale:
873            return ENGINE_AUTH_STALE;
874        }
875
876        if (xattrRead && xattrSysRead) {
877            context.xtocSemantics = XtocSemantics::All;
878        } else if (xattrRead) {
879            context.xtocSemantics = XtocSemantics::User;
880        } else if (xattrSysRead) {
881            context.xtocSemantics = XtocSemantics::System;
882        } else {
883            return ENGINE_EACCESS;
884        }
885    }
886    return ENGINE_SUCCESS;
887}
888
889static ENGINE_ERROR_CODE validate_xattr_privilege(SubdocCmdContext& context) {
890    auto key = context.get_xattr_key();
891    if (key.empty()) {
892        return ENGINE_SUCCESS;
893    }
894
895    if (cb::xattr::is_vattr(key)) {
896        return validate_vattr_privilege(context);
897    }
898
899    cb::rbac::Privilege privilege;
900    // We've got an XATTR..
901    if (context.traits.is_mutator) {
902        if (cb::xattr::is_system_xattr(key)) {
903            privilege = cb::rbac::Privilege::SystemXattrWrite;
904        } else {
905            privilege = cb::rbac::Privilege::XattrWrite;
906        }
907    } else {
908        if (cb::xattr::is_system_xattr(key)) {
909            privilege = cb::rbac::Privilege::SystemXattrRead;
910        } else {
911            privilege = cb::rbac::Privilege::XattrRead;
912        }
913    }
914
915    auto access = context.connection.checkPrivilege(privilege, context.cookie);
916    switch (access) {
917    case cb::rbac::PrivilegeAccess::Ok:
918        return ENGINE_SUCCESS;
919    case cb::rbac::PrivilegeAccess::Fail:
920        return ENGINE_EACCESS;
921    case cb::rbac::PrivilegeAccess::Stale:
922        return ENGINE_AUTH_STALE;
923    }
924
925    throw std::logic_error(
926        "validate_xattr_privilege: invalid return value from checkPrivilege");
927}
928
929/**
930 * Replaces the xattrs on the document with the new ones provided
931 * @param new_xattr The new xattrs to use
932 * @param context The command context for this operation
933 * @param bodyoffset The offset in to the body of the xattr section
934 * @param bodysize The size of the body (excludes xattrs)
935 */
936static inline void replace_xattrs(const cb::char_buffer& new_xattr,
937                                  SubdocCmdContext& context,
938                                  const size_t bodyoffset,
939                                  const size_t bodysize) {
940    auto total = new_xattr.len + bodysize;
941
942    std::unique_ptr<char[]> full_document(new char[total]);
943    std::copy(
944            new_xattr.buf, new_xattr.buf + new_xattr.len, full_document.get());
945    std::copy(context.in_doc.buf + bodyoffset,
946              context.in_doc.buf + bodyoffset + bodysize,
947              full_document.get() + new_xattr.len);
948
949    context.temp_doc.swap(full_document);
950    context.in_doc = {context.temp_doc.get(), total};
951
952    if (new_xattr.empty()) {
953        context.in_datatype &= ~PROTOCOL_BINARY_DATATYPE_XATTR;
954        context.no_sys_xattrs = true;
955
956    } else {
957        context.in_datatype |= PROTOCOL_BINARY_DATATYPE_XATTR;
958    }
959}
960
961/**
962 * Delete user xattrs from the xattr blob if required.
963 * @param context The command context for this operation
964 * @return true if success and that we may progress to the
965 *              next phase
966 */
967static bool do_xattr_delete_phase(SubdocCmdContext& context) {
968    if (!context.do_delete_doc ||
969        !mcbp::datatype::is_xattr(context.in_datatype)) {
970        return true;
971    }
972
973    // We need to remove the user keys from the Xattrs and rebuild the document
974
975    const auto bodyoffset = cb::xattr::get_body_offset(context.in_doc);
976    const auto bodysize = context.in_doc.len - bodyoffset;
977
978    cb::char_buffer blob_buffer{(char*)context.in_doc.buf, (size_t)bodyoffset};
979
980    const cb::xattr::Blob xattr_blob(
981            blob_buffer, mcbp::datatype::is_snappy(context.in_datatype));
982
983    // The backing store for the blob is currently witin the actual
984    // document.. create a copy we can use for replace.
985    cb::xattr::Blob copy(xattr_blob);
986
987    // Remove the user xattrs so we're just left with system xattrs
988    copy.prune_user_keys();
989
990    const auto new_xattr = copy.finalize();
991    replace_xattrs(new_xattr, context, bodyoffset, bodysize);
992
993    return true;
994}
995
996/**
997 * Parse the XATTR blob and only operate on the single xattr
998 * requested
999 *
1000 * @param context The command context for this operation
1001 * @return true if success and that we may progress to the
1002 *              next phase
1003 */
1004static bool do_xattr_phase(SubdocCmdContext& context) {
1005    context.setCurrentPhase(SubdocCmdContext::Phase::XATTR);
1006    if (context.getOperations().empty()) {
1007        return true;
1008    }
1009
1010    // Does the user have the permission to perform XATTRs
1011    auto access = validate_xattr_privilege(context);
1012    if (access != ENGINE_SUCCESS) {
1013        access = context.connection.remapErrorCode(access);
1014        if (access == ENGINE_DISCONNECT) {
1015            context.connection.setState(McbpStateMachine::State::closing);
1016            return false;
1017        }
1018
1019        switch (context.traits.path) {
1020        case SubdocPath::SINGLE:
1021            // Failure of a (the only) op stops execution and returns an
1022            // error to the client.
1023            context.cookie.sendResponse(cb::engine_errc(access));
1024            return false;
1025
1026        case SubdocPath::MULTI:
1027            context.overall_status
1028                = PROTOCOL_BINARY_RESPONSE_SUBDOC_MULTI_PATH_FAILURE;
1029
1030            {
1031                // Mark all of them as failed..
1032                auto& operations = context.getOperations();
1033                for (auto op = operations.begin(); op != operations.end(); op++) {
1034                    op->status = engine_error_2_mcbp_protocol_error(access);
1035                }
1036            }
1037            return true;
1038        }
1039        throw std::logic_error("do_xattr_phase: unknown SubdocPath");
1040    }
1041
1042    auto bodysize = context.in_doc.len;
1043    auto bodyoffset = 0;
1044
1045    if (mcbp::datatype::is_xattr(context.in_datatype)) {
1046        bodyoffset = cb::xattr::get_body_offset(context.in_doc);;
1047        bodysize -= bodyoffset;
1048    }
1049
1050    cb::char_buffer blob_buffer{(char*)context.in_doc.buf, (size_t)bodyoffset};
1051
1052    const cb::xattr::Blob xattr_blob(
1053            blob_buffer, mcbp::datatype::is_snappy(context.in_datatype));
1054    auto key = context.get_xattr_key();
1055    auto value_buf = xattr_blob.get(key);
1056
1057    if (value_buf.len == 0) {
1058        context.xattr_buffer.reset(new char[2]);
1059        context.xattr_buffer[0] = '{';
1060        context.xattr_buffer[1] = '}';
1061        value_buf = {context.xattr_buffer.get(), 2};
1062    } else {
1063        // To allow the subjson do it's thing with the full xattrs
1064        // create a full json doc looking like: {\"xattr_key\":\"value\"};
1065        size_t total = 5 + key.len + value_buf.len;
1066        context.xattr_buffer.reset(new char[total]);
1067        char* ptr = context.xattr_buffer.get();
1068        memcpy(ptr, "{\"", 2);
1069        ptr += 2;
1070        memcpy(ptr, key.buf, key.len);
1071        ptr += key.len;
1072        memcpy(ptr, "\":", 2);
1073        ptr += 2;
1074        memcpy(ptr, value_buf.buf, value_buf.len);
1075        ptr += value_buf.len;
1076        *ptr = '}';
1077        value_buf = { context.xattr_buffer.get(), total};
1078    }
1079
1080    std::unique_ptr<char[]> temp_doc;
1081    cb::const_char_buffer document{value_buf.buf, value_buf.len};
1082
1083    context.generate_macro_padding(document, cb::xattr::macros::CAS);
1084    context.generate_macro_padding(document, cb::xattr::macros::SEQNO);
1085    context.generate_macro_padding(document, cb::xattr::macros::VALUE_CRC32C);
1086
1087    bool modified;
1088    auto datatype = PROTOCOL_BINARY_DATATYPE_JSON;
1089    if (!operate_single_doc(context, document, datatype, temp_doc, modified)) {
1090        // Something failed..
1091        return false;
1092    }
1093    // Xattr doc should always be json
1094    Expects(datatype == PROTOCOL_BINARY_DATATYPE_JSON);
1095
1096    if (context.overall_status != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1097        return true;
1098    }
1099
1100    // We didn't change anything in the document so just drop everything
1101    if (!modified) {
1102        return true;
1103    }
1104
1105    // Time to rebuild the full document.
1106    // As a temporary solution we did create a full JSON doc for the
1107    // xattr key, so we should strip off the key and just store the value.
1108
1109    // The backing store for the blob is currently witin the actual
1110    // document.. create a copy we can use for replace.
1111    cb::xattr::Blob copy(xattr_blob);
1112
1113    if (document.len > key.len) {
1114        const char* start = strchr(document.buf, ':') + 1;
1115        const char* end = document.buf + document.len - 1;
1116
1117        copy.set(key, {start, size_t(end - start)});
1118    } else {
1119        copy.remove(key);
1120    }
1121    const auto new_xattr = copy.finalize();
1122    replace_xattrs(new_xattr, context, bodyoffset, bodysize);
1123
1124    return true;
1125}
1126
1127/**
1128 * Operate on the user body part of the document as specified by the command
1129 * context.
1130 * @return true if the command was successful (and execution should continue),
1131 *         else false.
1132 */
1133static bool do_body_phase(SubdocCmdContext& context) {
1134    context.setCurrentPhase(SubdocCmdContext::Phase::Body);
1135
1136    if (context.getOperations().empty()) {
1137        return true;
1138    }
1139
1140    size_t xattrsize = 0;
1141    cb::const_char_buffer document {context.in_doc.buf,
1142                                    context.in_doc.len};
1143
1144    if (mcbp::datatype::is_xattr(context.in_datatype)) {
1145        // We shouldn't have any documents like that!
1146        xattrsize = cb::xattr::get_body_offset(document);
1147        document.buf += xattrsize;
1148        document.len -= xattrsize;
1149    }
1150
1151    std::unique_ptr<char[]> temp_doc;
1152    bool modified;
1153
1154    if (!operate_single_doc(
1155                context, document, context.in_datatype, temp_doc, modified)) {
1156        return false;
1157    }
1158
1159    // We didn't change anything in the document so just drop everything
1160    if (!modified) {
1161        return true;
1162    }
1163
1164    // There isn't any xattrs associated with the document. We shouldn't
1165    // reallocate and move things around but just reuse the temporary
1166    // buffer we've already created.
1167    if (xattrsize == 0) {
1168        context.temp_doc.swap(temp_doc);
1169        context.in_doc = { context.temp_doc.get(), document.len };
1170        return true;
1171    }
1172
1173    // Time to rebuild the full document.
1174    auto total = xattrsize + document.len;
1175    std::unique_ptr<char[]> full_document(new char[total]);;
1176
1177    memcpy(full_document.get(), context.in_doc.buf, xattrsize);
1178    memcpy(full_document.get() + xattrsize, document.buf, document.len);
1179
1180    context.temp_doc.swap(full_document);
1181    context.in_doc = { context.temp_doc.get(), total };
1182
1183    return true;
1184}
1185
1186// Operate on the document as specified by the command context.
1187// Returns true if the command was successful (and execution should continue),
1188// else false.
1189static bool subdoc_operate(SubdocCmdContext& context) {
1190    if (context.executed) {
1191        return true;
1192    }
1193
1194    GenericBlockTimer<TimingHistogram, 0> bt(
1195            &all_buckets[context.connection.getBucketIndex()].subjson_operation_times);
1196
1197    context.overall_status = PROTOCOL_BINARY_RESPONSE_SUCCESS;
1198
1199    try {
1200        if (do_xattr_phase(context) && do_xattr_delete_phase(context) &&
1201            do_body_phase(context)) {
1202            context.executed = true;
1203            return true;
1204        }
1205    } catch (const std::bad_alloc&) {
1206        // Insufficient memory - unable to continue.
1207        context.cookie.sendResponse(cb::mcbp::Status::Enomem);
1208        return false;
1209    }
1210
1211    return false;
1212}
1213
1214// Update the engine with whatever modifications the subdocument command made
1215// to the document.
1216// Returns true if the update was successful (and execution should continue),
1217// else false.
1218static ENGINE_ERROR_CODE subdoc_update(SubdocCmdContext& context,
1219                                       ENGINE_ERROR_CODE ret, const char* key,
1220                                       size_t keylen, uint16_t vbucket,
1221                                       uint32_t expiration) {
1222    auto& connection = context.connection;
1223    auto& cookie = context.cookie;
1224
1225    if (context.getCurrentPhase() == SubdocCmdContext::Phase::XATTR) {
1226        LOG_WARNING(
1227                "Internal error: We should not reach subdoc_update in the "
1228                "xattr phase");
1229        return ENGINE_FAILED;
1230    }
1231
1232    if (!context.traits.is_mutator) {
1233        // No update required - just make sure we have the correct cas to use
1234        // for response.
1235        cookie.setCas(context.in_cas);
1236        return ENGINE_SUCCESS;
1237    }
1238
1239    // For multi-mutations, we only want to actually update the engine if /all/
1240    // paths succeeded - otherwise the document is unchanged (and we continue
1241    // to subdoc_response() to send information back to the client on what
1242    // succeeded/failed.
1243    if (context.overall_status != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1244        return ENGINE_SUCCESS;
1245    }
1246
1247    // Allocate a new item of this size.
1248    if (context.out_doc == NULL &&
1249        !(context.no_sys_xattrs && context.do_delete_doc)) {
1250
1251        if (ret == ENGINE_SUCCESS) {
1252            context.out_doc_len = context.in_doc.len;
1253            DocKey allocate_key(reinterpret_cast<const uint8_t*>(key),
1254                                keylen, connection.getDocNamespace());
1255
1256            const size_t priv_bytes =
1257                cb::xattr::get_system_xattr_size(context.in_datatype,
1258                                                 context.in_doc);
1259
1260            // Calculate the updated document length - use the last operation result.
1261            try {
1262                auto r = bucket_allocate_ex(cookie,
1263                                            allocate_key,
1264                                            context.out_doc_len,
1265                                            priv_bytes,
1266                                            context.in_flags,
1267                                            expiration,
1268                                            context.in_datatype,
1269                                            vbucket);
1270                if (r.first) {
1271                    // Save the allocated document in the cmd context.
1272                    context.out_doc = std::move(r.first);
1273                    ret = ENGINE_SUCCESS;
1274                } else {
1275                    ret = ENGINE_ENOMEM;
1276                }
1277            } catch (const cb::engine_error& e) {
1278                ret = ENGINE_ERROR_CODE(e.code().value());
1279                ret = context.connection.remapErrorCode(ret);
1280            }
1281        }
1282
1283        switch (ret) {
1284        case ENGINE_SUCCESS:
1285            // Save the allocated document in the cmd context.
1286            break;
1287
1288        case ENGINE_EWOULDBLOCK:
1289            cookie.setEwouldblock(true);
1290            return ret;
1291
1292        case ENGINE_DISCONNECT:
1293            connection.setState(McbpStateMachine::State::closing);
1294            return ret;
1295
1296        default:
1297            cookie.sendResponse(cb::engine_errc(ret));
1298            return ret;
1299        }
1300
1301        // To ensure we only replace the version of the document we
1302        // just appended to; set the CAS to the one retrieved from.
1303        bucket_item_set_cas(cookie, context.out_doc.get(), context.in_cas);
1304
1305        // Obtain the item info (and it's iovectors)
1306        item_info new_doc_info;
1307        if (!bucket_get_item_info(
1308                    cookie, context.out_doc.get(), &new_doc_info)) {
1309            cookie.sendResponse(cb::mcbp::Status::Einternal);
1310            return ENGINE_FAILED;
1311        }
1312
1313        // Copy the new document into the item.
1314        char* write_ptr = static_cast<char*>(new_doc_info.value[0].iov_base);
1315        std::memcpy(write_ptr, context.in_doc.buf, context.in_doc.len);
1316    }
1317
1318    // And finally, store the new document.
1319    uint64_t new_cas;
1320    mutation_descr_t mdt;
1321    auto new_op = context.needs_new_doc ? OPERATION_ADD : OPERATION_CAS;
1322    if (context.do_delete_doc && context.no_sys_xattrs) {
1323        new_cas = context.in_cas;
1324        DocKey docKey(reinterpret_cast<const uint8_t*>(key),
1325                      keylen,
1326                      connection.getDocNamespace());
1327        ret = bucket_remove(cookie, docKey, new_cas, vbucket, mdt);
1328    } else {
1329        ret = bucket_store(cookie,
1330                           context.out_doc.get(),
1331                           new_cas,
1332                           new_op,
1333                           context.do_delete_doc ? DocumentState::Deleted
1334                                                 : context.in_document_state);
1335    }
1336    ret = connection.remapErrorCode(ret);
1337    switch (ret) {
1338    case ENGINE_SUCCESS:
1339        // Record the UUID / Seqno if MUTATION_SEQNO feature is enabled so
1340        // we can include it in the response.
1341        if (connection.isSupportsMutationExtras()) {
1342            if (context.do_delete_doc && context.no_sys_xattrs) {
1343                context.vbucket_uuid = mdt.vbucket_uuid;
1344                context.sequence_no = mdt.seqno;
1345            } else {
1346                item_info info;
1347                if (!bucket_get_item_info(
1348                            cookie, context.out_doc.get(), &info)) {
1349                    LOG_WARNING("{}: Subdoc: Failed to get item info",
1350                                connection.getId());
1351                    cookie.sendResponse(cb::mcbp::Status::Einternal);
1352                    return ENGINE_FAILED;
1353                }
1354
1355                context.vbucket_uuid = info.vbucket_uuid;
1356                context.sequence_no = info.seqno;
1357            }
1358        }
1359
1360        cookie.setCas(new_cas);
1361        break;
1362
1363    case ENGINE_NOT_STORED:
1364        // If we tried an add for the item (because it didn't exists)
1365        // we might race with another thread which started to add
1366        // the document at the same time. (Note that for Set operations we
1367        // have to use "add" to add the item to avoid race conditions with
1368        // another thread trying to create the item at the same time.
1369        //
1370        // Adding documents will return NOT_STORED if the document already
1371        // exist in the database. In the context of a Set operation we map
1372        // the return code to EEXISTS which may cause the operation to be
1373        // retried.
1374        if (new_op == OPERATION_ADD &&
1375            context.mutationSemantics == MutationSemantics::Set) {
1376            ret = ENGINE_KEY_EEXISTS;
1377        }
1378        break;
1379
1380    case ENGINE_KEY_EEXISTS:
1381        // CAS mismatch. Caller may choose to retry this (without necessarily
1382        // telling the client), so send so response here...
1383        break;
1384
1385    case ENGINE_EWOULDBLOCK:
1386        cookie.setEwouldblock(true);
1387        break;
1388
1389    case ENGINE_DISCONNECT:
1390        connection.setState(McbpStateMachine::State::closing);
1391        break;
1392
1393    default:
1394        cookie.sendResponse(cb::engine_errc(ret));
1395        break;
1396    }
1397
1398    return ret;
1399}
1400
1401/* Encodes the context's mutation sequence number and vBucket UUID into the
1402 * given buffer.
1403 * @param descr Buffer to write to. Must be 16 bytes in size.
1404 */
1405static void encode_mutation_descr(SubdocCmdContext& context, char* buffer)
1406{
1407    mutation_descr_t descr;
1408    descr.seqno = htonll(context.sequence_no);
1409    descr.vbucket_uuid = htonll(context.vbucket_uuid);
1410    std::memcpy(buffer, &descr, sizeof(descr));
1411}
1412
1413/* Encodes the specified multi-mutation result into the given the given buffer.
1414 * @param index The operation index.
1415 * @param op Operation spec to encode.
1416 * @param buffer Buffer to encode into
1417 * @return The number of bytes written into the buffer.
1418 */
1419static size_t encode_multi_mutation_result_spec(uint8_t index,
1420                                                const SubdocCmdContext::OperationSpec& op,
1421                                                char* buffer)
1422{
1423    char* cursor = buffer;
1424
1425    // Always encode the index and status.
1426    *reinterpret_cast<uint8_t*>(cursor) = index;
1427    cursor += sizeof(uint8_t);
1428    *reinterpret_cast<uint16_t*>(cursor) = htons(op.status);
1429    cursor += sizeof(uint16_t);
1430
1431    // Also encode resultlen if status is success.
1432    if (op.status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1433        const auto& mloc = op.result.matchloc();
1434        *reinterpret_cast<uint32_t*>(cursor) =
1435                htonl(gsl::narrow<uint32_t>(mloc.length));
1436        cursor += sizeof(uint32_t);
1437    }
1438    return cursor - buffer;
1439}
1440
1441/* Construct and send a response to a single-path request back to the client.
1442 */
1443static void subdoc_single_response(Cookie& cookie, SubdocCmdContext& context) {
1444    auto& connection = context.connection;
1445
1446    context.response_val_len = 0;
1447    cb::const_char_buffer value = {};
1448    if (context.traits.responseHasValue()) {
1449        // The value may have been created in the xattr or the body phase
1450        // so it should only be one, so if it isn't an xattr it should be
1451        // in the body
1452        SubdocCmdContext::Phase phase = SubdocCmdContext::Phase::XATTR;
1453        if (context.getOperations(phase).empty()) {
1454            phase = SubdocCmdContext::Phase::Body;
1455        }
1456        auto mloc = context.getOperations(phase)[0].result.matchloc();
1457        value = {mloc.at, mloc.length};
1458        context.response_val_len = value.size();
1459    }
1460
1461    if (context.traits.is_mutator) {
1462        cb::audit::document::add(cookie,
1463                                 cb::audit::document::Operation::Modify);
1464    } else {
1465        cb::audit::document::add(cookie, cb::audit::document::Operation::Read);
1466    }
1467
1468    // Add mutation descr to response buffer if requested.
1469    cb::const_char_buffer extras = {};
1470    mutation_descr_t descr = {};
1471    if (connection.isSupportsMutationExtras() && context.traits.is_mutator) {
1472        encode_mutation_descr(context, reinterpret_cast<char*>(&descr));
1473        extras = {reinterpret_cast<const char*>(&descr), sizeof(descr)};
1474    }
1475
1476    auto status_code = cb::mcbp::Status::Success;
1477    if (context.in_document_state == DocumentState::Deleted) {
1478        status_code = cb::mcbp::Status::SubdocSuccessDeleted;
1479    }
1480
1481    cookie.sendResponse(cb::mcbp::Status(status_code),
1482                        extras,
1483                        {},
1484                        value,
1485                        context.traits.responseDatatype(context.in_datatype),
1486                        cookie.getCas());
1487}
1488
1489/* Construct and send a response to a multi-path mutation back to the client.
1490 */
1491static void subdoc_multi_mutation_response(Cookie& cookie,
1492                                           SubdocCmdContext& context) {
1493    auto& connection = context.connection;
1494
1495    // MULTI_MUTATION: On success, zero to N multi_mutation_result_spec objects
1496    // (one for each spec which wants to return a value), with optional 16byte
1497    // mutation descriptor in extras if MUTATION_SEQNO is enabled.
1498    //
1499    // On failure body indicates the index and status code of the first failing
1500    // spec.
1501    DynamicBuffer& response_buf = cookie.getDynamicBuffer();
1502    size_t extlen = 0;
1503    char* extras_ptr = nullptr;
1504
1505    // Encode mutation extras into buffer if success & they were requested.
1506    if (context.overall_status == PROTOCOL_BINARY_RESPONSE_SUCCESS &&
1507            connection.isSupportsMutationExtras()) {
1508        extlen = sizeof(mutation_descr_t);
1509        if (!response_buf.grow(extlen)) {
1510            // Unable to form complete response.
1511            cookie.sendResponse(cb::mcbp::Status::Enomem);
1512            return;
1513        }
1514        extras_ptr = response_buf.getCurrent();
1515        encode_mutation_descr(context, extras_ptr);
1516        response_buf.moveOffset(extlen);
1517    }
1518
1519    // Calculate how much space we need in our dynamic buffer, and total body
1520    // size to encode into the header.
1521    size_t response_buf_needed;
1522    size_t iov_len = 0;
1523    if (context.overall_status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1524        cb::audit::document::add(cookie,
1525                                 cb::audit::document::Operation::Modify);
1526
1527        // on success, one per each non-zero length result.
1528        response_buf_needed = 0;
1529        for (auto phase : phases) {
1530            for (size_t ii = 0;
1531                 ii < context.getOperations(phase).size(); ii++) {
1532                const auto& op = context.getOperations(phase)[ii];
1533                const auto mloc = op.result.matchloc();
1534                if (op.traits.responseHasValue() && mloc.length > 0) {
1535                    response_buf_needed += sizeof(uint8_t) + sizeof(uint16_t) +
1536                                           sizeof(uint32_t);
1537                    iov_len += mloc.length;
1538                }
1539            }
1540        }
1541    } else {
1542        // Just one - index and status of first failure.
1543        response_buf_needed = sizeof(uint8_t) + sizeof(uint16_t);
1544    }
1545
1546    // We need two iovecs per operation result:
1547    // 1. result_spec header (index, status; resultlen for successful specs).
1548    //    Use the dynamicBuffer for this.
1549    // 2. actual value - this already resides in the Subdoc::Result.
1550    if (!response_buf.grow(response_buf_needed)) {
1551        // Unable to form complete response.
1552        cookie.sendResponse(cb::mcbp::Status::Enomem);
1553        return;
1554    }
1555
1556    auto status_code = context.overall_status;
1557    if ((status_code == PROTOCOL_BINARY_RESPONSE_SUCCESS) &&
1558        (context.in_document_state == DocumentState::Deleted)) {
1559        status_code = PROTOCOL_BINARY_RESPONSE_SUBDOC_SUCCESS_DELETED;
1560    }
1561
1562    // Allocated required resource - build the header.
1563    mcbp_add_header(
1564            cookie,
1565            status_code,
1566            gsl::narrow<uint8_t>(extlen),
1567            /*keylen*/ 0,
1568            gsl::narrow<uint32_t>(extlen + response_buf_needed + iov_len),
1569            PROTOCOL_BINARY_RAW_BYTES);
1570
1571    // Append extras if requested.
1572    if (extlen > 0) {
1573        connection.addIov(reinterpret_cast<void*>(extras_ptr), extlen);
1574    }
1575
1576    // Append the iovecs for each operation result.
1577    uint8_t index = 0;
1578    for (auto phase : phases) {
1579        for (size_t ii = 0; ii < context.getOperations(phase).size(); ii++, index++) {
1580            const auto& op = context.getOperations(phase)[ii];
1581            // Successful - encode all non-zero length results.
1582            if (context.overall_status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1583                const auto mloc = op.result.matchloc();
1584                if (op.traits.responseHasValue() && mloc.length > 0) {
1585                    char* header = response_buf.getCurrent();
1586                    size_t header_sz =
1587                            encode_multi_mutation_result_spec(index, op, header);
1588
1589                    connection.addIov(reinterpret_cast<void*>(header),
1590                                      header_sz);
1591                    connection.addIov(mloc.at, mloc.length);
1592
1593                    response_buf.moveOffset(header_sz);
1594                }
1595            } else {
1596                // Failure - encode first unsuccessful path index and status.
1597                if (op.status != PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1598                    char* header = response_buf.getCurrent();
1599                    size_t header_sz =
1600                            encode_multi_mutation_result_spec(index, op, header);
1601
1602                    connection.addIov(reinterpret_cast<void*>(header),
1603                                      header_sz);
1604                    response_buf.moveOffset(header_sz);
1605
1606                    // Only the first unsuccessful op is reported.
1607                    break;
1608                }
1609            }
1610        }
1611    }
1612    connection.setState(McbpStateMachine::State::send_data);
1613}
1614
1615/* Construct and send a response to a multi-path lookup back to the client.
1616 */
1617static void subdoc_multi_lookup_response(Cookie& cookie,
1618                                         SubdocCmdContext& context) {
1619    auto& connection = context.connection;
1620
1621    // Calculate the value length - sum of all the operation results.
1622    context.response_val_len = 0;
1623    for (auto phase : phases) {
1624        for (auto& op : context.getOperations(phase)) {
1625            // 16bit status, 32bit resultlen, variable-length result.
1626            size_t result_size = sizeof(uint16_t) + sizeof(uint32_t);
1627            if (op.traits.responseHasValue()) {
1628                result_size += op.result.matchloc().length;
1629            }
1630            context.response_val_len += result_size;
1631        }
1632    }
1633
1634    // We need two iovecs per operation result:
1635    // 1. status (uin16_t) & vallen (uint32_t). Use the dynamicBuffer for this
1636    // 2. actual value - this already resides either in the original document
1637    //                   (for lookups) or stored in the Subdoc::Result.
1638    DynamicBuffer& response_buf = cookie.getDynamicBuffer();
1639    size_t needed = (sizeof(uint16_t) + sizeof(uint32_t)) *
1640        (context.getOperations(SubdocCmdContext::Phase::XATTR).size() +
1641         context.getOperations(SubdocCmdContext::Phase::Body).size());
1642
1643    if (!response_buf.grow(needed)) {
1644        // Unable to form complete response.
1645        cookie.sendResponse(cb::mcbp::Status::Enomem);
1646        return;
1647    }
1648
1649    // Allocated required resource - build the header.
1650    auto status_code = context.overall_status;
1651    if (status_code == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
1652        cb::audit::document::add(cookie, cb::audit::document::Operation::Read);
1653        if (context.in_document_state == DocumentState::Deleted) {
1654            status_code = PROTOCOL_BINARY_RESPONSE_SUBDOC_SUCCESS_DELETED;
1655        }
1656    }
1657
1658    // Lookups to a deleted document which (partially) succeeded need
1659    // to be mapped MULTI_PATH_FAILURE_DELETED, so the client knows the found
1660    // document was in Deleted state.
1661    if (status_code == PROTOCOL_BINARY_RESPONSE_SUBDOC_MULTI_PATH_FAILURE &&
1662            (context.in_document_state == DocumentState::Deleted) &&
1663            !context.traits.is_mutator) {
1664        status_code = PROTOCOL_BINARY_RESPONSE_SUBDOC_MULTI_PATH_FAILURE_DELETED;
1665    }
1666
1667    mcbp_add_header(cookie,
1668                    status_code,
1669                    /*extlen*/ 0, /*keylen*/
1670                    0,
1671                    gsl::narrow<uint32_t>(context.response_val_len),
1672                    PROTOCOL_BINARY_RAW_BYTES);
1673
1674    // Append the iovecs for each operation result.
1675    for (auto phase : phases) {
1676        for (auto& op : context.getOperations(phase)) {
1677            auto mloc = op.result.matchloc();
1678
1679            // Header is always included. Result value included if the response for
1680            // this command has a value (e.g. not for EXISTS).
1681            char* header = response_buf.getCurrent();
1682            const size_t header_sz = sizeof(uint16_t) + sizeof(uint32_t);
1683            *reinterpret_cast<uint16_t*>(header) = htons(op.status);
1684            uint32_t result_len = 0;
1685            if (op.traits.responseHasValue()) {
1686                result_len = htonl(uint32_t(mloc.length));
1687            }
1688            *reinterpret_cast<uint32_t*>(header +
1689                                         sizeof(uint16_t)) = result_len;
1690
1691            connection.addIov(reinterpret_cast<void*>(header), header_sz);
1692
1693            if (result_len != 0) {
1694                connection.addIov(mloc.at, mloc.length);
1695            }
1696            response_buf.moveOffset(header_sz);
1697        }
1698    }
1699
1700    connection.setState(McbpStateMachine::State::send_data);
1701}
1702
1703// Respond back to the user as appropriate to the specific command.
1704static void subdoc_response(Cookie& cookie, SubdocCmdContext& context) {
1705    switch (context.traits.path) {
1706    case SubdocPath::SINGLE:
1707        subdoc_single_response(cookie, context);
1708        return;
1709
1710    case SubdocPath::MULTI:
1711        if (context.traits.is_mutator) {
1712            subdoc_multi_mutation_response(cookie, context);
1713        } else {
1714            subdoc_multi_lookup_response(cookie, context);
1715        }
1716        return;
1717    }
1718
1719    // Shouldn't get here - invalid traits.path
1720    cookie.sendResponse(cb::mcbp::Status::Einternal);
1721    auto& connection = cookie.getConnection();
1722    LOG_WARNING(
1723            "{}: subdoc_response - invalid traits.path - closing connection {}",
1724            connection.getId(),
1725            connection.getDescription());
1726    connection.setWriteAndGo(McbpStateMachine::State::closing);
1727}
1728
1729void subdoc_get_executor(Cookie& cookie) {
1730    return subdoc_executor(cookie,
1731                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_GET>());
1732}
1733
1734void subdoc_exists_executor(Cookie& cookie) {
1735    return subdoc_executor(cookie,
1736                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_EXISTS>());
1737}
1738
1739void subdoc_dict_add_executor(Cookie& cookie) {
1740    return subdoc_executor(cookie,
1741                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_DICT_ADD>());
1742}
1743
1744void subdoc_dict_upsert_executor(Cookie& cookie) {
1745    return subdoc_executor(
1746            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_DICT_UPSERT>());
1747}
1748
1749void subdoc_delete_executor(Cookie& cookie) {
1750    return subdoc_executor(cookie,
1751                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_DELETE>());
1752}
1753
1754void subdoc_replace_executor(Cookie& cookie) {
1755    return subdoc_executor(cookie,
1756                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_REPLACE>());
1757}
1758
1759void subdoc_array_push_last_executor(Cookie& cookie) {
1760    return subdoc_executor(
1761            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_LAST>());
1762}
1763
1764void subdoc_array_push_first_executor(Cookie& cookie) {
1765    return subdoc_executor(
1766            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_FIRST>());
1767}
1768
1769void subdoc_array_insert_executor(Cookie& cookie) {
1770    return subdoc_executor(
1771            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_INSERT>());
1772}
1773
1774void subdoc_array_add_unique_executor(Cookie& cookie) {
1775    return subdoc_executor(
1776            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_ADD_UNIQUE>());
1777}
1778
1779void subdoc_counter_executor(Cookie& cookie) {
1780    return subdoc_executor(cookie,
1781                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_COUNTER>());
1782}
1783
1784void subdoc_get_count_executor(Cookie& cookie) {
1785    return subdoc_executor(cookie,
1786                           get_traits<PROTOCOL_BINARY_CMD_SUBDOC_GET_COUNT>());
1787}
1788
1789void subdoc_multi_lookup_executor(Cookie& cookie) {
1790    return subdoc_executor(
1791            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_MULTI_LOOKUP>());
1792}
1793
1794void subdoc_multi_mutation_executor(Cookie& cookie) {
1795    return subdoc_executor(
1796            cookie, get_traits<PROTOCOL_BINARY_CMD_SUBDOC_MULTI_MUTATION>());
1797}
1798