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