"Fossies" - the Fresh Open Source Software Archive

Member "irods-4.2.8/plugins/api/src/atomic_apply_metadata_operations.cpp" (14 May 2020, 24224 Bytes) of package /linux/misc/irods-4.2.8.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "atomic_apply_metadata_operations.cpp" see the Fossies "Dox" file reference documentation.

    1 #include "api_plugin_number.h"
    2 #include "irods_configuration_keywords.hpp"
    3 #include "rodsDef.h"
    4 #include "rcConnect.h"
    5 #include "rodsErrorTable.h"
    6 #include "rodsPackInstruct.h"
    7 #include "client_api_whitelist.hpp"
    8 
    9 #include "apiHandler.hpp"
   10 
   11 #include <functional>
   12 #include <stdexcept>
   13 
   14 #ifdef RODS_SERVER
   15 
   16 //
   17 // Server-side Implementation
   18 //
   19 
   20 #include "atomic_apply_metadata_operations.h"
   21 
   22 #include "rodsConnect.h"
   23 #include "objDesc.hpp"
   24 #include "irods_stacktrace.hpp"
   25 #include "irods_server_api_call.hpp"
   26 #include "irods_re_serialization.hpp"
   27 #include "irods_get_l1desc.hpp"
   28 #include "irods_rs_comm_query.hpp"
   29 #include "irods_get_full_path_for_config_file.hpp"
   30 #include "irods_query.hpp"
   31 #include "miscServerFunct.hpp"
   32 
   33 #define IRODS_FILESYSTEM_ENABLE_SERVER_SIDE_API
   34 #include "filesystem.hpp"
   35 
   36 #include "json.hpp"
   37 #include "fmt/format.h"
   38 #include "nanodbc/nanodbc.h"
   39 
   40 #include <cstdlib>
   41 #include <string>
   42 #include <string_view>
   43 #include <tuple>
   44 #include <chrono>
   45 #include <system_error>
   46 
   47 namespace
   48 {
   49     // clang-format off
   50     namespace fs    = irods::experimental::filesystem;
   51 
   52     using json      = nlohmann::json;
   53     using operation = std::function<int(rsComm_t*, bytesBuf_t*, bytesBuf_t**)>;
   54     // clang-format on
   55 
   56     //
   57     // Function Prototypes
   58     //
   59 
   60     auto call_atomic_apply_metadata_operations(irods::api_entry*, rsComm_t*, bytesBuf_t*, bytesBuf_t**) -> int;
   61 
   62     auto is_input_valid(const bytesBuf_t*) -> std::tuple<bool, std::string>;
   63 
   64     auto to_bytes_buffer(const std::string& _s) -> bytesBuf_t*;
   65 
   66     auto get_file_descriptor(const bytesBuf_t& _buf) -> int;
   67 
   68     auto make_error_object(const json& _op, int _op_index, const std::string& _error_msg) -> json;
   69 
   70     auto get_object_id(rsComm_t& _comm, const std::string& _entity_name, const std::string& _entity_type) -> int;
   71 
   72     auto new_database_connection() -> std::tuple<std::string, nanodbc::connection>;
   73 
   74     auto user_has_permission_to_modify_metadata(rsComm_t& _comm,
   75                                                 nanodbc::connection& _db_conn,
   76                                                 int _object_id,
   77                                                 const std::string& _entity_type) -> bool;
   78 
   79     auto execute_transaction(nanodbc::connection& _db_conn, std::function<int(nanodbc::transaction&)> _func) -> int;
   80 
   81     auto get_meta_id(nanodbc::connection& _db_conn, const fs::metadata& _metadata) -> int;
   82 
   83     auto is_metadata_attached_to_object(nanodbc::connection& _db_conn, int _object_id, int _meta_id) -> bool;
   84 
   85     auto insert_metadata(nanodbc::connection& _db_conn, std::string_view _db_instance_name, const fs::metadata& _metadata) -> int;
   86 
   87     auto attach_metadata_to_object(nanodbc::connection& _db_conn, int _object_id, int _meta_id) -> void;
   88 
   89     auto detach_metadata_from_object(nanodbc::connection& _db_conn, int _object_id, int _meta_id) -> void;
   90 
   91     auto execute_metadata_operation(nanodbc::connection& _db_conn,
   92                                     std::string_view _db_instance_name,
   93                                     int _object_id,
   94                                     const json& _operation,
   95                                     int _op_index) -> std::tuple<int, bytesBuf_t*>;
   96 
   97     auto rs_atomic_apply_metadata_operations(rsComm_t*, bytesBuf_t*, bytesBuf_t**) -> int;
   98 
   99     //
  100     // Function Implementations
  101     //
  102 
  103     auto call_atomic_apply_metadata_operations(irods::api_entry* _api,
  104                                               rsComm_t* _comm,
  105                                               bytesBuf_t* _input,
  106                                               bytesBuf_t** _output) -> int
  107     {
  108         return _api->call_handler<bytesBuf_t*, bytesBuf_t**>(_comm, _input, _output);
  109     }
  110 
  111     auto is_input_valid(const bytesBuf_t* _input) -> std::tuple<bool, std::string>
  112     {
  113         if (!_input) {
  114             return {false, "Missing JSON input"};
  115         }
  116 
  117         if (_input->len <= 0) {
  118             return {false, "Length of buffer must be greater than zero"};
  119         }
  120 
  121         if (!_input->buf) {
  122             return {false, "Missing input buffer"};
  123         }
  124 
  125         return {true, ""};
  126     }
  127 
  128     auto to_bytes_buffer(const std::string& _s) -> bytesBuf_t*
  129     {
  130         constexpr auto allocate = [](const auto bytes) noexcept
  131         {
  132             return std::memset(std::malloc(bytes), 0, bytes);
  133         };
  134 
  135         const auto buf_size = _s.length() + 1;
  136 
  137         auto* buf = static_cast<char*>(allocate(sizeof(char) * buf_size));
  138         std::strncpy(buf, _s.c_str(), _s.length());
  139 
  140         auto* bbp = static_cast<bytesBuf_t*>(allocate(sizeof(bytesBuf_t)));
  141         bbp->len = buf_size;
  142         bbp->buf = buf;
  143 
  144         return bbp;
  145     }
  146 
  147     auto make_error_object(const json& _op, int _op_index, const std::string& _error_msg) -> json
  148     {
  149         return json{
  150             {"operation", _op},
  151             {"operation_index", _op_index},
  152             {"error_message", _error_msg}
  153         };
  154     }
  155 
  156     auto get_object_id(rsComm_t& _comm, const std::string& _entity_name, const std::string& _entity_type) -> int
  157     {
  158         std::string gql;
  159 
  160         if (_entity_type == "collection") {
  161             gql = fmt::format("select COLL_ID where COLL_NAME = '{}'", _entity_name);
  162         }
  163         else if (_entity_type == "data_object") {
  164             fs::path p = _entity_name;
  165             gql = fmt::format("select DATA_ID where COLL_NAME = '{}' and DATA_NAME = '{}'",
  166                               p.parent_path().c_str(),
  167                               p.object_name().c_str());
  168         }
  169         else if (_entity_type == "user") {
  170             gql = fmt::format("select USER_ID where USER_NAME = '{}'", _entity_name);
  171         }
  172         else if (_entity_type == "resource") {
  173             gql = fmt::format("select RESC_ID where RESC_NAME = '{}'", _entity_name);
  174         }
  175         else {
  176             throw std::runtime_error{fmt::format("Invalid entity type specified [entity_type => {}]", _entity_type)};
  177         }
  178 
  179         for (auto&& row : irods::query{&_comm, gql}) {
  180             return std::stoi(row[0]);
  181         }
  182 
  183         throw std::runtime_error{fmt::format("Entity does not exist [entity_name => {}]", _entity_name)};
  184     }
  185 
  186     auto new_database_connection() -> std::tuple<std::string, nanodbc::connection>
  187     {
  188         const std::string dsn = [] {
  189             if (const char* dsn = std::getenv("irodsOdbcDSN"); dsn) {
  190                 return dsn;
  191             }
  192 
  193             return "iRODS Catalog";
  194         }();
  195 
  196         std::string config_path;
  197 
  198         if (const auto error = irods::get_full_path_for_config_file("server_config.json", config_path); !error.ok()) {
  199             rodsLog(LOG_ERROR, "Server configuration not found");
  200             throw std::runtime_error{"Failed to connect to catalog"};
  201         }
  202 
  203         rodsLog(LOG_DEBUG, "Reading server configuration ...");
  204 
  205         json config;
  206 
  207         {
  208             std::ifstream config_file{config_path};
  209             config_file >> config;
  210         }
  211 
  212         try {
  213             const auto& db_plugin_config = config.at(irods::CFG_PLUGIN_CONFIGURATION_KW).at(irods::PLUGIN_TYPE_DATABASE);
  214             const auto& db_instance = db_plugin_config.front();
  215             const auto db_username = db_instance.at(irods::CFG_DB_USERNAME_KW).get<std::string>();
  216             const auto db_password = db_instance.at(irods::CFG_DB_PASSWORD_KW).get<std::string>();
  217 
  218             // Capture the database instance name.
  219             std::string db_instance_name;
  220             for (auto& [k, v] : db_plugin_config.items()) {
  221                 db_instance_name = k;
  222             }
  223 
  224             if (db_instance_name.empty()) {
  225                 throw std::runtime_error{"Database instance name cannot be empty"};
  226             }
  227 
  228             nanodbc::connection db_conn{dsn, db_username, db_password};
  229 
  230             if (db_instance_name == "mysql") {
  231                 // MySQL must be running in ANSI mode (or at least in PIPES_AS_CONCAT mode) to be
  232                 // able to understand Postgres SQL. STRICT_TRANS_TABLES must be set too, otherwise
  233                 // inserting NULL into a "NOT NULL" column does not produce an error.
  234                 nanodbc::just_execute(db_conn, "set SESSION sql_mode = 'ANSI,STRICT_TRANS_TABLES'");
  235                 nanodbc::just_execute(db_conn, "set character_set_client = utf8");
  236                 nanodbc::just_execute(db_conn, "set character_set_results = utf8");
  237                 nanodbc::just_execute(db_conn, "set character_set_connection = utf8");
  238             }
  239 
  240             return {db_instance_name, db_conn};
  241         }
  242         catch (const std::exception& e) {
  243             rodsLog(LOG_ERROR, e.what());
  244             throw std::runtime_error{"Failed to connect to catalog"};
  245         }
  246     }
  247 
  248     auto user_has_permission_to_modify_metadata(rsComm_t& _comm,
  249                                                 nanodbc::connection& _db_conn,
  250                                                 int _object_id,
  251                                                 const std::string& _entity_type) -> bool
  252     {
  253         if (_entity_type == "data_object" || _entity_type == "collection") {
  254             const auto query = fmt::format("select t.token_id from R_TOKN_MAIN t"
  255                                            " inner join R_OBJT_ACCESS a on t.token_id = a.access_type_id "
  256                                            "where"
  257                                            " a.user_id = (select user_id from R_USER_MAIN where user_name = '{}') and"
  258                                            " a.object_id = '{}'", _comm.clientUser.userName, _object_id);
  259 
  260             if (auto row = execute(_db_conn, query); row.next()) {
  261                 constexpr int access_modify_object = 1120;
  262                 return row.get<int>(0) >= access_modify_object;
  263             }
  264         }
  265         else if (_entity_type == "user" || _entity_type == "resource") {
  266             return irods::is_privileged_client(_comm);
  267         }
  268         else {
  269             rodsLog(LOG_ERROR, "Invalid entity type [entity_type => %s]", _entity_type.c_str());
  270         }
  271 
  272         return false;
  273     }
  274 
  275     auto execute_transaction(nanodbc::connection& _db_conn, std::function<int(nanodbc::transaction&)> _func) -> int
  276     {
  277         nanodbc::transaction trans{_db_conn};
  278         return _func(trans);
  279     }
  280 
  281     auto get_meta_id(nanodbc::connection& _db_conn, const fs::metadata& _metadata) -> int
  282     {
  283         nanodbc::statement stmt{_db_conn};
  284 
  285         prepare(stmt, "select meta_id from R_META_MAIN "
  286                       "where"
  287                       " meta_attr_name = ? and"
  288                       " meta_attr_value = ? and"
  289                       " meta_attr_unit = ?");
  290 
  291         stmt.bind(0, _metadata.attribute.c_str());
  292         stmt.bind(1, _metadata.value.c_str());
  293         stmt.bind(2, _metadata.units.c_str());
  294 
  295         if (auto row = execute(stmt); row.next()) {
  296             return row.get<int>(0);
  297         }
  298 
  299         return -1;
  300     }
  301 
  302     auto is_metadata_attached_to_object(nanodbc::connection& _db_conn, int _object_id, int _meta_id) -> bool
  303     {
  304         nanodbc::statement stmt{_db_conn};
  305 
  306         prepare(stmt, "select count(*) from R_OBJT_METAMAP where object_id = ? and meta_id = ?");
  307 
  308         stmt.bind(0, &_object_id);
  309         stmt.bind(1, &_meta_id);
  310 
  311         if (auto row = execute(stmt); row.next()) {
  312             return row.get<int>(0) > 0;
  313         }
  314 
  315         return false;
  316     }
  317 
  318     auto insert_metadata(nanodbc::connection& _db_conn, std::string_view _db_instance_name, const fs::metadata& _metadata) -> int
  319     {
  320         nanodbc::statement stmt{_db_conn};
  321 
  322         if (_db_instance_name == "oracle") {
  323             prepare(stmt, "insert into R_META_MAIN (meta_id, meta_attr_name, meta_attr_value, meta_attr_unit, create_ts, modify_ts) "
  324                           "values (select R_OBJECTID.nextval from DUAL, ?, ?, ?, ?, ?)");
  325         }
  326         else if (_db_instance_name == "mysql") {
  327             prepare(stmt, "insert into R_META_MAIN (meta_id, meta_attr_name, meta_attr_value, meta_attr_unit, create_ts, modify_ts) "
  328                           "values (R_OBJECTID_nextval(), ?, ?, ?, ?, ?)");
  329         }
  330         else if (_db_instance_name == "postgres") {
  331             prepare(stmt, "insert into R_META_MAIN (meta_id, meta_attr_name, meta_attr_value, meta_attr_unit, create_ts, modify_ts) "
  332                           "values (nextval('R_OBJECTID'), ?, ?, ?, ?, ?)");
  333         }
  334         else {
  335             throw std::runtime_error{"Invalid database plugin configuration"};
  336         }
  337 
  338         using std::chrono::system_clock;
  339         using std::chrono::duration_cast;
  340         using std::chrono::seconds;
  341 
  342         const auto timestamp = fmt::format("{:011}", duration_cast<seconds>(system_clock::now().time_since_epoch()).count());
  343 
  344         stmt.bind(0, _metadata.attribute.c_str());
  345         stmt.bind(1, _metadata.value.c_str());
  346         stmt.bind(2, _metadata.units.c_str());
  347         stmt.bind(3, timestamp.c_str());
  348         stmt.bind(4, timestamp.c_str());
  349 
  350         execute(stmt);
  351 
  352         return get_meta_id(_db_conn, _metadata);
  353     }
  354 
  355     auto attach_metadata_to_object(nanodbc::connection& _db_conn, int _object_id, int _meta_id) -> void
  356     {
  357         nanodbc::statement stmt{_db_conn};
  358 
  359         prepare(stmt, "insert into R_OBJT_METAMAP (object_id, meta_id, create_ts, modify_ts) "
  360                       "values (?, ?, ?, ?)");
  361 
  362         using std::chrono::system_clock;
  363         using std::chrono::duration_cast;
  364         using std::chrono::seconds;
  365 
  366         const auto timestamp = fmt::format("{:011}", duration_cast<seconds>(system_clock::now().time_since_epoch()).count());
  367 
  368         stmt.bind(0, &_object_id);
  369         stmt.bind(1, &_meta_id);
  370         stmt.bind(2, timestamp.c_str());
  371         stmt.bind(3, timestamp.c_str());
  372 
  373         execute(stmt);
  374     }
  375 
  376     auto detach_metadata_from_object(nanodbc::connection& _db_conn, int _object_id, int _meta_id) -> void
  377     {
  378         nanodbc::statement stmt{_db_conn};
  379 
  380         prepare(stmt, "delete from R_OBJT_METAMAP where object_id = ? and meta_id = ?");
  381 
  382         stmt.bind(0, &_object_id);
  383         stmt.bind(1, &_meta_id);
  384 
  385         execute(stmt);
  386     }
  387 
  388     auto execute_metadata_operation(nanodbc::connection& _db_conn,
  389                                     std::string_view _db_instance_name,
  390                                     int _object_id,
  391                                     const json& _op,
  392                                     int _op_index) -> std::tuple<int, bytesBuf_t*>
  393     {
  394         try {
  395             fs::metadata md;
  396 
  397             md.attribute = _op.at("attribute").get<std::string>();
  398             md.value = _op.at("value").get<std::string>();
  399 
  400             // "units" are optional.
  401             if (_op.count("units")) {
  402                 md.units = _op.at("units").get<std::string>();
  403             }
  404 
  405             if (const auto op_code = _op.at("operation").get<std::string>(); op_code == "add") {
  406                 if (auto meta_id = get_meta_id(_db_conn, md); meta_id > -1) {
  407                     if (!is_metadata_attached_to_object(_db_conn, _object_id, meta_id)) {
  408                         attach_metadata_to_object(_db_conn, _object_id, meta_id);
  409                     }
  410                 }
  411                 else if (meta_id = insert_metadata(_db_conn, _db_instance_name, md); meta_id > -1) {
  412                     attach_metadata_to_object(_db_conn, _object_id, meta_id);
  413                 }
  414                 else {
  415                     const auto msg = fmt::format("Failed to insert metadata [attribute => {}, value => {}, units => {}]",
  416                                                  md.attribute, md.value, md.units);
  417                     return {SYS_INTERNAL_ERR, to_bytes_buffer(make_error_object(_op, _op_index, msg).dump())};
  418                 }
  419             }
  420             else if (op_code == "remove") {
  421                 if (const auto meta_id = get_meta_id(_db_conn, md); meta_id > -1) {
  422                     detach_metadata_from_object(_db_conn, _object_id, meta_id);
  423                 }
  424             }
  425             else {
  426                 rodsLog(LOG_ERROR, "Invalid metadata operation [metadata_operation => %s]", _op.dump().c_str());
  427                 return {INVALID_OPERATION, to_bytes_buffer(make_error_object(_op, _op_index, "Invalid metadata operation.").dump())};
  428             }
  429 
  430             return {0, to_bytes_buffer("{}")};
  431         }
  432         catch (const fs::filesystem_error& e) {
  433             rodsLog(LOG_ERROR, "%s [metadata_operation => %s]", e.what(), _op.dump().c_str());
  434             return {e.code().value(), to_bytes_buffer(make_error_object(_op, _op_index, e.what()).dump())};
  435         }
  436         catch (const json::out_of_range& e) {
  437             rodsLog(LOG_ERROR, "%s [metadata_operation => %s]", e.what(), _op.dump().c_str());
  438             return {SYS_INTERNAL_ERR, to_bytes_buffer(make_error_object(_op, _op_index, e.what()).dump())};
  439         }
  440         catch (const json::type_error& e) {
  441             rodsLog(LOG_ERROR, "%s [metadata_operation => %s]", e.what(), _op.dump().c_str());
  442             return {SYS_INTERNAL_ERR, to_bytes_buffer(make_error_object(_op, _op_index, e.what()).dump())};
  443         }
  444         catch (const std::system_error& e) {
  445             rodsLog(LOG_ERROR, "%s [metadata_operation => %s]", e.what(), _op.dump().c_str());
  446             return {e.code().value(), to_bytes_buffer(make_error_object(_op, _op_index, e.what()).dump())};
  447         }
  448     }
  449 
  450     auto rs_atomic_apply_metadata_operations(rsComm_t* _comm, bytesBuf_t* _input, bytesBuf_t** _output) -> int
  451     {
  452         rodsServerHost_t *rodsServerHost;
  453 
  454         if (const auto ec = getAndConnRcatHost(_comm, MASTER_RCAT, nullptr, &rodsServerHost); ec < 0) {
  455             return ec;
  456         }
  457 
  458         if (LOCAL_HOST == rodsServerHost->localFlag) {
  459             std::string svc_role;
  460 
  461             if (const auto err = get_catalog_service_role(svc_role); !err.ok()) {
  462                 const auto* msg = "Failed to retrieve service role";
  463                 rodsLog(LOG_ERROR, msg);
  464                 *_output = to_bytes_buffer(make_error_object(json{}, 0, msg).dump());
  465                 return err.code();
  466             }
  467 
  468             if (irods::CFG_SERVICE_ROLE_CONSUMER == svc_role) {
  469                 const auto* msg = "Remote catalog provider not found";
  470                 rodsLog(LOG_ERROR, msg);
  471                 *_output = to_bytes_buffer(make_error_object(json{}, 0, msg).dump());
  472                 return SYS_NO_ICAT_SERVER_ERR;
  473             }
  474 
  475             if (irods::CFG_SERVICE_ROLE_PROVIDER != svc_role) {
  476                 const auto msg = fmt::format("Role not supported [role => {}]", svc_role);
  477                 rodsLog(LOG_ERROR, msg.c_str());
  478                 *_output = to_bytes_buffer(make_error_object(json{}, 0, msg).dump());
  479                 return SYS_SERVICE_ROLE_NOT_SUPPORTED;
  480             }
  481         }
  482         else {
  483             std::string_view json_input(static_cast<const char*>(_input->buf), _input->len);
  484             char* json_output = nullptr;
  485 
  486             const auto ec = rc_atomic_apply_metadata_operations(rodsServerHost->conn, json_input.data(), &json_output);
  487             *_output = to_bytes_buffer(json_output);
  488 
  489             return ec;
  490         }
  491 
  492         if (const auto [valid, msg] = is_input_valid(_input); !valid) {
  493             rodsLog(LOG_ERROR, msg.c_str());
  494             *_output = to_bytes_buffer(make_error_object(json{}, 0, "Invalid input").dump());
  495             return INPUT_ARG_NOT_WELL_FORMED_ERR;
  496         }
  497 
  498         json input;
  499 
  500         try {
  501             input = json::parse(std::string(static_cast<const char*>(_input->buf), _input->len));
  502         }
  503         catch (const json::parse_error& e) {
  504             rodsLog(LOG_ERROR, "Failed to parse input into JSON [error message => %s]", e.what());
  505 
  506             const auto err_info = make_error_object(json{}, 0, e.what());
  507             *_output = to_bytes_buffer(err_info.dump());
  508 
  509             return INPUT_ARG_NOT_WELL_FORMED_ERR;
  510         }
  511 
  512         std::string entity_name;
  513         std::string entity_type;
  514         int object_id = -1;
  515 
  516         try {
  517             entity_name = input.at("entity_name").get<std::string>();
  518             entity_type = input.at("entity_type").get<std::string>();
  519             object_id = get_object_id(*_comm, entity_name, entity_type);
  520         }
  521         catch (const std::exception& e) {
  522             *_output = to_bytes_buffer(make_error_object(json{}, 0, e.what()).dump());
  523             return SYS_INVALID_INPUT_PARAM;
  524         }
  525 
  526         std::string db_instance_name;;
  527         nanodbc::connection db_conn;
  528 
  529         try {
  530             std::tie(db_instance_name, db_conn) = new_database_connection();
  531         }
  532         catch (const std::exception& e) {
  533             *_output = to_bytes_buffer(make_error_object(json{}, 0, e.what()).dump());
  534             return SYS_CONFIG_FILE_ERR;
  535         }
  536 
  537         if (!user_has_permission_to_modify_metadata(*_comm, db_conn, object_id, entity_type)) {
  538             rodsLog(LOG_ERROR, "User not allowed to modify metadata [entity_name => %s, entity_type => %s, object_id => %i",
  539                     entity_name.c_str(), entity_type.c_str(), object_id);
  540             *_output = to_bytes_buffer(make_error_object(json{}, 0, "User not allowed to modify metadata").dump());
  541             return CAT_NO_ACCESS_PERMISSION;
  542         }
  543 
  544         return execute_transaction(db_conn, [&](auto& _trans) -> int
  545         {
  546             try {
  547                 const auto& operations = input.at("operations");
  548 
  549                 for (json::size_type i = 0; i < operations.size(); ++i) {
  550                     const auto [ec, bbuf] = execute_metadata_operation(_trans.connection(),
  551                                                                        db_instance_name,
  552                                                                        object_id,
  553                                                                        operations[i],
  554                                                                        i);
  555                     
  556                     if (ec != 0) {
  557                         *_output = bbuf;
  558                         return ec;
  559                     }
  560                 }
  561 
  562                 _trans.commit();
  563 
  564                 *_output = to_bytes_buffer("{}");
  565 
  566                 return 0;
  567             }
  568             catch (const json::type_error& e) {
  569                 *_output = to_bytes_buffer(make_error_object(json{}, 0, e.what()).dump());
  570                 return SYS_INTERNAL_ERR;
  571             }
  572         });
  573     }
  574 
  575     const operation op = rs_atomic_apply_metadata_operations;
  576     #define CALL_ATOMIC_APPLY_METADATA_OPERATIONS call_atomic_apply_metadata_operations
  577 } // anonymous namespace
  578 
  579 #else // RODS_SERVER
  580 
  581 //
  582 // Client-side Implementation
  583 //
  584 
  585 namespace
  586 {
  587     using operation = std::function<int(rsComm_t*, bytesBuf_t*, bytesBuf_t**)>;
  588     const operation op{};
  589     #define CALL_ATOMIC_APPLY_METADATA_OPERATIONS nullptr
  590 } // anonymous namespace
  591 
  592 #endif // RODS_SERVER
  593 
  594 // The plugin factory function must always be defined.
  595 extern "C"
  596 auto plugin_factory(const std::string& _instance_name,
  597                     const std::string& _context) -> irods::api_entry*
  598 {
  599 #ifdef RODS_SERVER
  600     irods::client_api_whitelist::instance().add(ATOMIC_APPLY_METADATA_OPERATIONS_APN);
  601 #endif // RODS_SERVER
  602 
  603     // clang-format off
  604     irods::apidef_t def{ATOMIC_APPLY_METADATA_OPERATIONS_APN,       // API number
  605                         RODS_API_VERSION,                           // API version
  606                         NO_USER_AUTH,                               // Client auth
  607                         NO_USER_AUTH,                               // Proxy auth
  608                         "BytesBuf_PI", 0,                           // In PI / bs flag
  609                         "BytesBuf_PI", 0,                           // Out PI / bs flag
  610                         op,                                         // Operation
  611                         "atomic_apply_metadata_operations",         // Operation name
  612                         nullptr,                                    // Null clear function
  613                         (funcPtr) CALL_ATOMIC_APPLY_METADATA_OPERATIONS};
  614     // clang-format on
  615 
  616     auto* api = new irods::api_entry{def};
  617 
  618     api->in_pack_key = "BytesBuf_PI";
  619     api->in_pack_value = BytesBuf_PI;
  620 
  621     api->out_pack_key = "BytesBuf_PI";
  622     api->out_pack_value = BytesBuf_PI;
  623 
  624     return api;
  625 }
  626