datasrc_clients_mgr.h 22.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#ifndef DATASRC_CLIENTS_MGR_H
#define DATASRC_CLIENTS_MGR_H 1

18
#include <util/threads/thread.h>
19
#include <util/threads/sync.h>
20

21
22
23
#include <log/logger_support.h>
#include <log/log_dbglevels.h>

24
25
#include <dns/rrclass.h>

26
#include <cc/data.h>
27
28

#include <datasrc/data_source.h>
29
#include <datasrc/client_list.h>
30
#include <datasrc/memory/zone_writer.h>
31

32
#include <auth/auth_log.h>
33
#include <auth/datasrc_config.h>
34

35
#include <boost/array.hpp>
36
#include <boost/bind.hpp>
37
#include <boost/shared_ptr.hpp>
38
#include <boost/noncopyable.hpp>
39

40
#include <exception>
41
#include <cassert>
42
43
44
45
46
47
#include <list>
#include <utility>

namespace isc {
namespace auth {

48
namespace datasrc_clientmgr_internal {
49
50
51
52
53
54
// This namespace is essentially private for DataSrcClientsMgr(Base) and
// DataSrcClientsBuilder(Base).  This is exposed in the public header
// only because these classes are templated (for testing purposes) and
// class internal has to be defined here.

/// \brief ID of commands from the DataSrcClientsMgr to DataSrcClientsBuilder.
55
enum CommandID {
56
    NOOP,         ///< Do nothing.  Only useful for tests; no argument
Jelte Jansen's avatar
Jelte Jansen committed
57
58
59
    RECONFIGURE,  ///< Reconfigure the datasource client lists,
                  ///  the argument to the command is the full new
                  ///  datasources configuration.
60
61
62
    LOADZONE,     ///< Load a new version of zone into a memory,
                  ///  the argument to the command is a map containing 'class'
                  ///  and 'origin' elements, both should have been validated.
63
64
    SHUTDOWN,     ///< Shutdown the builder; no argument
    NUM_COMMANDS
65
};
66
67
68
69
70
71
72

/// \brief The data type passed from DataSrcClientsMgr to
/// DataSrcClientsBuilder.
///
/// The first element of the pair is the command ID, and the second element
/// is its argument.  If the command doesn't take an argument it should be
/// a null pointer.
73
typedef std::pair<CommandID, data::ConstElementPtr> Command;
74
} // namespace datasrc_clientmgr_internal
75

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/// \brief Frontend to the manager object for data source clients.
///
/// This class provides interfaces for configuring and updating a set of
/// data source clients "in the background".  The user of this class can
/// assume any operation on this class can be done effectively non-blocking,
/// not suspending any delay-sensitive operations such as DNS query
/// processing.  The only exception is the time when this class object
/// is destroyed (normally as a result of an implicit call to the destructor);
/// in the current implementation it can take time depending on what is
/// running "in the background" at the time of the call.
///
/// Internally, an object of this class invokes a separate thread to perform
/// time consuming operations such as loading large zone data into memory,
/// but such details are completely hidden from the user of this class.
///
/// This class is templated only so that we can test the class without
/// involving actual threads or mutex.  Normal applications will only
/// need one specific specialization that has a typedef of
/// \c DataSrcClientsMgr.
95
96
template <typename ThreadType, typename BuilderType, typename MutexType,
          typename CondVarType>
97
class DataSrcClientsMgrBase : boost::noncopyable {
98
99
100
101
102
private:
    typedef std::map<dns::RRClass,
                     boost::shared_ptr<datasrc::ConfigurableClientList> >
    ClientListsMap;

103
public:
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
    /// \brief Thread-safe accessor to the data source client lists.
    ///
    /// This class provides a simple wrapper for searching the client lists
    /// stored in the DataSrcClientsMgr in a thread-safe manner.
    /// It ensures the result of \c getClientList() can be used without
    /// causing a race condition with other threads that can possibly use
    /// the same manager throughout the lifetime of the holder object.
    ///
    /// This also means the holder object is expected to have a short lifetime.
    /// The application shouldn't try to keep it unnecessarily long.
    /// It's normally expected to create the holder object on the stack
    /// of a small scope and automatically let it be destroyed at the end
    /// of the scope.
    class Holder {
    public:
        Holder(DataSrcClientsMgrBase& mgr) :
            mgr_(mgr), locker_(mgr_.map_mutex_)
        {}

        /// \brief Find a data source client list of a specified RR class.
        ///
        /// It returns a pointer to the list stored in the manager if found,
        /// otherwise it returns NULL.  The manager keeps the ownership of
        /// the pointed object.  Also, it's not safe to get access to the
        /// object beyond the scope of the holder object.
129
130
131
132
133
134
135
136
137
        ///
        /// \note Since the ownership isn't transferred the return value
        /// could be a bare pointer (and it's probably better in several
        /// points).  Unfortunately, some unit tests currently don't work
        /// unless this method effectively shares the ownership with the
        /// tests.  That's the only reason why we return a shared pointer
        /// for now.  We should eventually fix it and change the return value
        /// type (see Trac ticket #2395).  Other applications must not
        /// assume the ownership is actually shared.
138
        boost::shared_ptr<datasrc::ConfigurableClientList> findClientList(
139
140
141
142
143
            const dns::RRClass& rrclass)
        {
            const ClientListsMap::const_iterator
                it = mgr_.clients_map_->find(rrclass);
            if (it == mgr_.clients_map_->end()) {
144
                return (boost::shared_ptr<datasrc::ConfigurableClientList>());
145
            } else {
146
                return (it->second);
147
148
149
150
151
152
153
            }
        }
    private:
        DataSrcClientsMgrBase& mgr_;
        typename MutexType::Locker locker_;
    };

154
155
156
157
158
159
160
161
162
163
164
165
    /// \brief Constructor.
    ///
    /// It internally invokes a separate thread and waits for further
    /// operations from the user application.
    ///
    /// This method is basically exception free except in case of really
    /// rare system-level errors.  When that happens the only reasonable
    /// action that the application can take would be to terminate the program
    /// in practice.
    ///
    /// \throw std::bad_alloc internal memory allocation failure.
    /// \throw isc::Unexpected general unexpected system errors.
166
    DataSrcClientsMgrBase() :
167
        clients_map_(new ClientListsMap),
Jelte Jansen's avatar
Jelte Jansen committed
168
169
        builder_(&command_queue_, &cond_, &queue_mutex_, &clients_map_,
                 &map_mutex_),
170
        builder_thread_(boost::bind(&BuilderType::run, &builder_))
171
    {}
172
173
174
175
176
177
178
179
180
181
182
183

    /// \brief The destructor.
    ///
    /// It tells the internal thread to stop and waits for it completion.
    /// In the current implementation, it can block for some unpredictably
    /// long period depending on what the thread is doing at that time
    /// (in future we may want to implement a rapid way of killing the thread
    /// and/or provide a separate interface for waiting so that the application
    /// can choose the timing).
    ///
    /// The waiting operation can result in an exception, but this method
    /// catches any of them so this method itself is exception free.
184
    ~DataSrcClientsMgrBase() {
185
186
187
        // We share class member variables with the builder, which will be
        // invalidated after the call to the destructor, so we need to make
        // sure the builder thread is terminated.  Depending on the timing
188
189
190
        // this could take a long time; if we don't want that to happen in
        // this context, we may want to introduce a separate 'shutdown()'
        // method.
191
192
193
194
        // Also, since we don't want to propagate exceptions from a destructor,
        // we catch any possible ones.  In fact the only really expected one
        // is Thread::UncaughtException when the builder thread died due to
        // an exception.  We specifically log it and just ignore others.
195
        try {
196
197
            sendCommand(datasrc_clientmgr_internal::SHUTDOWN,
                        data::ConstElementPtr());
198
            builder_thread_.wait();
199
        } catch (const util::thread::Thread::UncaughtException& ex) {
200
201
202
            // technically, logging this could throw, which will be propagated.
            // But such an exception would be a fatal one anyway, so we
            // simply let it go through.
203
204
            LOG_ERROR(auth_logger, AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR).
                arg(ex.what());
205
206
207
208
        } catch (...) {
            LOG_ERROR(auth_logger,
                      AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR);
        }
209
210

        cleanup();              // see below
211
212
    }

213
214
215
216
    /// \brief Handle new full configuration for data source clients.
    ///
    /// This method simply passes the new configuration to the builder
    /// and immediately returns.  This method is basically exception free
217
218
    /// as long as the caller passes a non NULL value for \c config_arg;
    /// it doesn't validate the argument further.
219
220
221
222
223
224
225
226
227
228
    ///
    /// \brief isc::InvalidParameter config_arg is NULL.
    /// \brief std::bad_alloc
    ///
    /// \param config_arg The new data source configuration.  Must not be NULL.
    void reconfigure(data::ConstElementPtr config_arg) {
        if (!config_arg) {
            isc_throw(InvalidParameter, "Invalid null config argument");
        }
        sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg);
229
        reconfigureHook();      // for test's customization
230
231
    }

232
    /// \brief Set the underlying data source client lists to new lists.
233
234
235
236
    ///
    /// This is provided only for some existing tests until we support a
    /// cleaner way to use faked data source clients.  Non test code or
    /// newer tests must not use this.
237
    void setDataSrcClientLists(datasrc::ClientListMapPtr new_lists) {
238
        typename MutexType::Locker locker(map_mutex_);
239
        clients_map_ = new_lists;
240
241
    }

242
private:
243
244
245
246
247
248
    // This is expected to be called at the end of the destructor.  It
    // actually does nothing, but provides a customization point for
    // specialized class for tests so that the tests can inspect the last
    // state of the class.
    void cleanup() {}

249
250
251
    // same as cleanup(), for reconfigure().
    void reconfigureHook() {}

252
    void sendCommand(datasrc_clientmgr_internal::CommandID command,
253
254
                     data::ConstElementPtr arg)
    {
255
256
257
258
259
260
        // The lock will be held until the end of this method.  Only
        // push_back has to be protected, but we can avoid having an extra
        // block this way.
        typename MutexType::Locker locker(queue_mutex_);
        command_queue_.push_back(
            datasrc_clientmgr_internal::Command(command, arg));
261
        cond_.signal();
262
    }
263

264
265
266
267
    //
    // The following are shared with the builder.
    //
    // The list is used as a one-way queue: back-in, front-out
268
    std::list<datasrc_clientmgr_internal::Command> command_queue_;
269
270
    CondVarType cond_;          // condition variable for queue operations
    MutexType queue_mutex_;     // mutex to protect the queue
271
    datasrc::ClientListMapPtr clients_map_;
Jelte Jansen's avatar
Jelte Jansen committed
272
273
                                // map of actual data source client objects
    MutexType map_mutex_;       // mutex to protect the clients map
274

275
    BuilderType builder_;
276
    ThreadType builder_thread_; // for safety this should be placed last
277
278
};

279
namespace datasrc_clientmgr_internal {
280
281
282
283
284

/// \brief A class that maintains a set of data source clients.
///
/// An object of this class is supposed to run on a dedicated thread, whose
/// main function is a call to its \c run() method.  It runs in a loop
285
/// waiting for commands from the manager and handles each command (including
286
/// reloading a new version of zone data into memory or fully reconfiguration
287
/// of specific set of data source clients).  When it receives a SHUTDOWN
288
289
/// command, it exits from the loop, which will terminate the thread.
///
290
291
292
/// While this class is defined in a publicly visible namespace, it's
/// essentially private to \c DataSrcClientsMgr.  Except for tests,
/// applications should not directly access this class.
293
294
295
///
/// This class is templated so that we can test it without involving actual
/// threads or locks.
296
template <typename MutexType, typename CondVarType>
297
class DataSrcClientsBuilderBase : boost::noncopyable {
298
299
300
301
302
private:
    typedef std::map<dns::RRClass,
                     boost::shared_ptr<datasrc::ConfigurableClientList> >
    ClientListsMap;

303
public:
304
    /// \brief Internal errors in handling commands.
305
306
307
308
    ///
    /// This exception is expected to be caught within the
    /// \c DataSrcClientsBuilder implementation, but is defined as public
    /// so tests can be checked it.
309
    class InternalCommandError : public isc::Exception {
310
    public:
311
        InternalCommandError(const char* file, size_t line, const char* what) :
312
313
314
            isc::Exception(file, line, what) {}
    };

315
316
317
318
319
320
321
322
    /// \brief Constructor.
    ///
    /// It simply sets up a local copy of shared data with the manager.
    ///
    /// Note: this will take actual set (map) of data source clients and
    /// a mutex object for it in #2210 or #2212.
    ///
    /// \throw None
323
    DataSrcClientsBuilderBase(std::list<Command>* command_queue,
324
                              CondVarType* cond, MutexType* queue_mutex,
325
                              datasrc::ClientListMapPtr* clients_map,
326
                              MutexType* map_mutex
327
        ) :
328
329
        command_queue_(command_queue), cond_(cond), queue_mutex_(queue_mutex),
        clients_map_(clients_map), map_mutex_(map_mutex)
330
331
    {}

332
    /// \brief The main loop.
333
334
    void run();

335
336
337
338
339
340
    /// \brief Handle one command from the manager.
    ///
    /// This is a dedicated subroutine of run() and is essentially private,
    /// but is defined as a separate public method so we can test each
    /// command test individually.  In any case, this class itself is
    /// generally considered private.
341
    ///
342
    /// \return true if the builder should keep running; false otherwise.
343
344
345
    bool handleCommand(const Command& command);

private:
346
347
    // NOOP command handler.  We use this so tests can override it; the default
    // implementation really does nothing.
348
349
    void doNoop() {}

Jelte Jansen's avatar
Jelte Jansen committed
350
    void doReconfigure(const data::ConstElementPtr& config) {
351
        if (config) {
Jelte Jansen's avatar
Jelte Jansen committed
352
353
            LOG_INFO(auth_logger,
                     AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_STARTED);
354
            try {
Jelte Jansen's avatar
Jelte Jansen committed
355
356
357
358
359
                // Define new_clients_map outside of the block that
                // has the lock scope; this way, after the swap,
                // the lock is guaranteed to be released before
                // the old data is destroyed, minimizing the lock
                // duration.
360
                datasrc::ClientListMapPtr new_clients_map =
361
                    configureDataSource(config);
Jelte Jansen's avatar
Jelte Jansen committed
362
363
364
365
366
367
368
369
                {
                    typename MutexType::Locker locker(*map_mutex_);
                    new_clients_map.swap(*clients_map_);
                } // lock is released by leaving scope
                LOG_INFO(auth_logger,
                         AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS);
            } catch (const datasrc::ConfigurableClientList::ConfigurationError&
                     config_error) {
Jelte Jansen's avatar
Jelte Jansen committed
370
371
                LOG_ERROR(auth_logger,
                    AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR).
Jelte Jansen's avatar
Jelte Jansen committed
372
                    arg(config_error.what());
373
            } catch (const datasrc::DataSourceError& ds_error) {
Jelte Jansen's avatar
Jelte Jansen committed
374
                LOG_ERROR(auth_logger,
375
376
377
                    AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_DATASRC_ERROR).
                    arg(ds_error.what());
            } catch (const isc::Exception& isc_error) {
Jelte Jansen's avatar
Jelte Jansen committed
378
379
                LOG_ERROR(auth_logger,
                    AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR).
380
                    arg(isc_error.what());
381
            }
382
383
384
            // other exceptions are propagated, see
            // http://bind10.isc.org/ticket/2210#comment:13

Jelte Jansen's avatar
Jelte Jansen committed
385
            // old clients_map_ data is released by leaving scope
386
387
388
        }
    }

389
390
    void doLoadZone(const isc::data::ConstElementPtr& arg);

391
    // The following are shared with the manager
392
393
394
    std::list<Command>* command_queue_;
    CondVarType* cond_;
    MutexType* queue_mutex_;
395
    datasrc::ClientListMapPtr* clients_map_;
396
    MutexType* map_mutex_;
397
398
399
400
401
402
};

// Shortcut typedef for normal use
typedef DataSrcClientsBuilderBase<util::thread::Mutex, util::thread::CondVar>
DataSrcClientsBuilder;

403
404
405
template <typename MutexType, typename CondVarType>
void
DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
406
    LOG_INFO(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_STARTED);
407

408
409
410
411
412
413
    try {
        bool keep_running = true;
        while (keep_running) {
            std::list<Command> current_commands;
            {
                // Move all new commands to local queue under the protection of
414
                // queue_mutex_.
415
416
417
418
                typename MutexType::Locker locker(*queue_mutex_);
                while (command_queue_->empty()) {
                    cond_->wait(*queue_mutex_);
                }
419
                current_commands.swap(*command_queue_);
Jelte Jansen's avatar
Jelte Jansen committed
420
            } // the lock is released here.
421
422

            while (keep_running && !current_commands.empty()) {
423
424
425
426
427
428
429
                try {
                    keep_running = handleCommand(current_commands.front());;
                } catch (const InternalCommandError& e) {
                    LOG_ERROR(auth_logger,
                              AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
                        arg(e.what());
                }
430
                current_commands.pop_front();
431
432
            }
        }
433

434
435
436
        LOG_INFO(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_STOPPED);
    } catch (const std::exception& ex) {
        // We explicitly catch exceptions so we can log it as soon as possible.
437
        LOG_FATAL(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_FAILED).
438
            arg(ex.what());
439
        std::terminate();
440
    } catch (...) {
441
        LOG_FATAL(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_FAILED_UNEXPECTED);
442
        std::terminate();
443
    }
444
445
446
447
448
449
450
}

template <typename MutexType, typename CondVarType>
bool
DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
    const Command& command)
{
451
452
453
454
455
    const CommandID cid = command.first;
    if (cid >= NUM_COMMANDS) {
        // This shouldn't happen except for a bug within this file.
        isc_throw(Unexpected, "internal bug: invalid command, ID: " << cid);
    }
456

457
    const boost::array<const char*, NUM_COMMANDS> command_desc = {
458
        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
459
460
461
    };
    LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
              AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
462
    switch (command.first) {
463
464
465
    case RECONFIGURE:
        doReconfigure(command.second);
        break;
466
467
468
    case LOADZONE:
        doLoadZone(command.second);
        break;
469
470
471
472
    case SHUTDOWN:
        return (false);
    case NOOP:
        doNoop();
473
474
475
        break;
    case NUM_COMMANDS:
        assert(false);          // we rejected this case above
476
477
478
    }
    return (true);
}
479
480
481
482
483
484

template <typename MutexType, typename CondVarType>
void
DataSrcClientsBuilderBase<MutexType, CondVarType>::doLoadZone(
    const isc::data::ConstElementPtr& arg)
{
JINMEI Tatuya's avatar
JINMEI Tatuya committed
485
486
487
    // We assume same basic level validation as this method can only be
    // called via the manager in practice.  manager is expected to do the
    // minimal validation.
488
    assert(arg);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
489
490
    assert(arg->get("class"));
    assert(arg->get("origin"));
491

492
493
494
495
    const dns::RRClass rrclass(arg->get("class")->stringValue());
    const dns::Name origin(arg->get("origin")->stringValue());
    ClientListsMap::iterator found = (*clients_map_)->find(rrclass);
    if (found == (*clients_map_)->end()) {
496
497
        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
                  "/" << rrclass << ": not configured for the class");
498
499
500
501
502
503
504
        return;
    }

    boost::shared_ptr<datasrc::ConfigurableClientList> client_list =
        found->second;
    assert(client_list);

505
    try {
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
        const datasrc::ConfigurableClientList::ZoneWriterPair writerpair =
            client_list->getCachedZoneWriter(origin);
        switch (writerpair.first) {
        case datasrc::ConfigurableClientList::ZONE_NOT_FOUND:
            isc_throw(InternalCommandError, "failed to load zone " << origin
                      << "/" << rrclass << ": not found in any configured "
                      "data source.");
        case datasrc::ConfigurableClientList::ZONE_NOT_CACHED:
            isc_throw(InternalCommandError, "failed to load zone " << origin
                      << "/" << rrclass << ": not served from memory");
        case datasrc::ConfigurableClientList::CACHE_DISABLED:
            // This is an internal error. Auth server must have the cache
            // enabled.
            isc_throw(InternalCommandError, "failed to load zone " << origin
                      << "/" << rrclass << ": internal failure, in-memory cache "
                      "is somehow disabled");
        default:
            break;
        }

        boost::shared_ptr<datasrc::memory::ZoneWriter> zwriter =
            writerpair.second;
        zwriter->load(); // this can take time but doesn't cause a race
        {   // install() can cause a race and must be in a critical section
            typename MutexType::Locker locker(*map_mutex_);
            zwriter->install();
        }
        LOG_DEBUG(auth_logger, DBG_AUTH_OPS,
                  AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE)
            .arg(origin).arg(rrclass);

        zwriter->cleanup();
    } catch (const InternalCommandError& ex) {
        throw;
540
541
542
543
544
545
546
    } catch (const isc::Exception& ex) {
        // We catch our internal exceptions (which will be just ignored) and
        // propagated others (which should generally be considered fatal and
        // will make the thread terminate)
        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
                  "/" << rrclass << ": error occurred in reload: " <<
                  ex.what());
547
548
    }
}
549
} // namespace datasrc_clientmgr_internal
550
551
552
553
554

/// \brief Shortcut type for normal data source clients manager.
///
/// In fact, for non test applications this is the only type of this kind
/// to be considered.
555
556
557
558
typedef DataSrcClientsMgrBase<
    util::thread::Thread,
    datasrc_clientmgr_internal::DataSrcClientsBuilder,
    util::thread::Mutex, util::thread::CondVar> DataSrcClientsMgr;
559
560
561
562
563
564
565
566
} // namespace auth
} // namespace isc

#endif  // DATASRC_CLIENTS_MGR_H

// Local Variables:
// mode: c++
// End: