master_loader.cc 17.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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.

#include <dns/master_loader.h>
16 17 18 19 20
#include <dns/master_lexer.h>
#include <dns/name.h>
#include <dns/rrttl.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
21
#include <dns/rdata.h>
22

23
#include <string>
24
#include <memory>
25
#include <vector>
26
#include <boost/algorithm/string/predicate.hpp> // for iequals
27
#include <boost/shared_ptr.hpp>
28

29
using std::string;
30
using std::auto_ptr;
31 32
using std::vector;
using std::pair;
33
using boost::algorithm::iequals;
34
using boost::shared_ptr;
35 36 37 38

namespace isc {
namespace dns {

39 40
namespace {

41 42 43 44 45 46 47 48 49 50
// An internal exception, used to control the code flow in case of errors.
// It is thrown during the loading and caught later, not to be propagated
// outside of the file.
class InternalException : public isc::Exception {
public:
    InternalException(const char* filename, size_t line, const char* what) :
        Exception(filename, line, what)
    {}
};

Jelte Jansen's avatar
Jelte Jansen committed
51
} // end unnamed namespace
52

53 54 55 56
class MasterLoader::MasterLoaderImpl {
public:
    MasterLoaderImpl(const char* master_file,
                     const Name& zone_origin,
57 58
                     const RRClass& zone_class,
                     const MasterLoaderCallbacks& callbacks,
59
                     const AddRRCallback& add_callback,
60 61 62
                     MasterLoader::Options options) :
        lexer_(),
        zone_origin_(zone_origin),
63
        active_origin_(zone_origin),
64 65
        zone_class_(zone_class),
        callbacks_(callbacks),
66
        add_callback_(add_callback),
67 68 69
        options_(options),
        master_file_(master_file),
        initialized_(false),
70
        ok_(true),
71
        many_errors_((options & MANY_ERRORS) != 0),
72
        previous_name_(false),
73 74
        complete_(false),
        seen_error_(false)
75 76
    {}

77 78 79
    void reportError(const std::string& filename, size_t line,
                     const std::string& reason)
    {
80
        seen_error_ = true;
81
        callbacks_.error(filename, line, reason);
82
        if (!many_errors_) {
83 84 85 86 87 88 89 90
            // In case we don't have the lenient mode, every error is fatal
            // and we throw
            ok_ = false;
            complete_ = true;
            isc_throw(MasterLoaderError, reason.c_str());
        }
    }

91 92 93
    void pushSource(const std::string& filename) {
        std::string error;
        if (!lexer_.pushSource(filename.c_str(), &error)) {
94
            if (initialized_) {
95
                isc_throw(InternalException, error.c_str());
96 97 98 99 100
            } else {
                // Top-level file
                reportError("", 0, error);
                ok_ = false;
            }
101
        }
102 103
        // Store the current status, so we can recover it upon popSource
        include_info_.push_back(IncludeInfo(active_origin_, last_name_));
104
        initialized_ = true;
105
        previous_name_ = false;
106 107
    }

108
    bool popSource() {
109
        if (lexer_.getSourceCount() == 1) {
110 111
            return (false);
        }
112
        lexer_.popSource();
113 114 115 116 117 118 119 120 121
        // Restore original origin and last seen name

        // We move in tandem, there's an extra item included during the
        // initialization, so we can never run out of them
        assert(!include_info_.empty());
        const IncludeInfo& info(include_info_.back());
        active_origin_ = info.first;
        last_name_ = info.second;
        include_info_.pop_back();
122
        previous_name_ = false;
123
        return (true);
124 125 126 127 128
    }

    void pushStreamSource(std::istream& stream) {
        lexer_.pushSource(stream);
        initialized_ = true;
129 130
    }

131 132
    // Get a string token. Handle it as error if it is not string.
    const string getString() {
133
        lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
134
        return (string_token_);
135 136
    }

137
    bool loadIncremental(size_t count_limit);
138

139 140
    MasterToken handleInitialToken();

141 142 143 144 145
    void doOrigin(bool is_optional) {
        // Parse and create the new origin. It is relative to the previous
        // one.
        const MasterToken&
            name_tok(lexer_.getNextToken(MasterToken::QSTRING, is_optional));
146 147 148

        if (name_tok.getType() == MasterToken::QSTRING ||
            name_tok.getType() == MasterToken::STRING) {
149

150 151 152 153
            const MasterToken::StringRegion&
                name_string(name_tok.getStringRegion());
            active_origin_ = Name(name_string.beg, name_string.len,
                                  &active_origin_);
154
        } else {
155 156 157 158 159 160 161
            // If it is not optional, we must not get anything but
            // a string token.
            assert(is_optional);

            // We return the newline there. This is because we want to
            // behave the same if there is or isn't the name, leaving the
            // newline there.
162 163
            lexer_.ungetToken();
        }
164 165
    }

166 167 168 169 170 171 172 173 174
    void doInclude() {
        // First, get the filename to include
        const string
            filename(lexer_.getNextToken(MasterToken::QSTRING).getString());

        // There optionally can be an origin, that applies before the include.
        doOrigin(true);

        pushSource(filename);
175 176
    }

177
    void handleDirective(const char* directive, size_t length) {
178
        if (iequals(directive, "INCLUDE")) {
179
            doInclude();
180
        } else if (iequals(directive, "ORIGIN")) {
181 182 183 184 185
            doOrigin(false);
            // The doOrigin doesn't do the cleanup of the line. This is
            // because it's shared with the doInclude and that one can't do
            // it.
            eatUntilEOL(true);
186
        } else if (iequals(directive, "TTL")) {
187 188 189 190 191 192 193 194 195
            // TODO: Implement
            isc_throw(isc::NotImplemented,
                      "TTL directive not implemented yet");
        } else {
            isc_throw(InternalException, "Unknown directive '" <<
                      string(directive, directive + length) << "'");
        }
    }

196 197 198 199 200 201 202 203
    void eatUntilEOL(bool reportExtra) {
        // We want to continue. Try to read until the end of line
        for (;;) {
            const MasterToken& token(lexer_.getNextToken());
            switch (token.getType()) {
                case MasterToken::END_OF_FILE:
                    callbacks_.warning(lexer_.getSourceName(),
                                       lexer_.getSourceLine(),
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
204
                                       "File does not end with newline");
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
                    // We don't pop here. The End of file will stay there,
                    // and we'll handle it in the next iteration of
                    // loadIncremental properly.
                    return;
                case MasterToken::END_OF_LINE:
                    // Found the end of the line. Good.
                    return;
                default:
                    // Some other type of token.
                    if (reportExtra) {
                        reportExtra = false;
                        reportError(lexer_.getSourceName(),
                                    lexer_.getSourceLine(),
                                    "Extra tokens at the end of line");
                    }
                    break;
            }
        }
    }

225 226
private:
    MasterLexer lexer_;
227
    const Name zone_origin_;
228 229
    Name active_origin_; // The origin used during parsing
                         // (modifiable by $ORIGIN)
230
    shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
231 232
    const RRClass zone_class_;
    MasterLoaderCallbacks callbacks_;
233
    AddRRCallback add_callback_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
234
    const MasterLoader::Options options_;
235
    const std::string master_file_;
236
    std::string string_token_;
237
    bool initialized_;
238 239 240
    bool ok_;                   // Is it OK to continue loading?
    const bool many_errors_;    // Are many errors allowed (or should we abort
                                // on the first)
241 242 243 244 245
    // Some info about the outer files from which we include.
    // The first one is current origin, the second is the last seen name
    // in that file.
    typedef pair<Name, shared_ptr<Name> > IncludeInfo;
    vector<IncludeInfo> include_info_;
246 247
    bool previous_name_; // True if there was a previous name in this file
                         // (false at the beginning or after an $INCLUDE line)
248
public:
249 250 251
    bool complete_;             // All work done.
    bool seen_error_;           // Was there at least one error during the
                                // load?
252 253
};

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
// A helper method of loadIncremental, parsing the first token of a new line.
// If it looks like an RR, detect its owner name and return a string token for
// the next field of the RR.
// Otherwise, return either END_OF_LINE or END_OF_FILE token depending on
// whether the loader continues to the next line or completes the load,
// respectively.  Other corner cases including $-directive handling is done
// here.
// For unexpected errors, it throws an exception, which will be handled in
// loadIncremental.
MasterToken
MasterLoader::MasterLoaderImpl::handleInitialToken() {
    const MasterToken& initial_token =
        lexer_.getNextToken(MasterLexer::QSTRING | MasterLexer::INITIAL_WS);

    // The most likely case is INITIAL_WS, and then string/qstring.  We
    // handle them first.
    if (initial_token.getType() == MasterToken::INITIAL_WS) {
        const MasterToken& next_token = lexer_.getNextToken();
        if (next_token.getType() == MasterToken::END_OF_LINE) {
            return (next_token); // blank line
        } else if (next_token.getType() == MasterToken::END_OF_FILE) {
            lexer_.ungetToken(); // handle it in the next iteration.
            eatUntilEOL(true);  // effectively warn about the unexpected EOF.
            return (MasterToken(MasterToken::END_OF_LINE));
        }

        // This means the same name as previous.
        if (last_name_.get() == NULL) {
            isc_throw(InternalException, "No previous name to use in "
                      "place of initial whitespace");
        } else if (!previous_name_) {
            callbacks_.warning(lexer_.getSourceName(),
                               lexer_.getSourceLine(),
                               "Ambiguous previous name previous name for "
                               "use in place of initial whitespace");
        }
        return (next_token);
    } else if (initial_token.getType() == MasterToken::STRING ||
               initial_token.getType() == MasterToken::QSTRING) {
        // If it is name (or directive), handle it.
        const MasterToken::StringRegion&
            name_string(initial_token.getStringRegion());

        if (name_string.len > 0 && name_string.beg[0] == '$') {
            // This should have either thrown (and the error handler
            // will read up until the end of line) or read until the
            // end of line.

            // Exclude the $ from the string on this point.
            handleDirective(name_string.beg + 1, name_string.len - 1);
            // So, get to the next line, there's nothing more interesting
            // in this one.
            return (MasterToken(MasterToken::END_OF_LINE));
        }

        // This should be an RR, starting with an owner name.  Construct the
        // name, and some string token should follow.
        last_name_.reset(new Name(name_string.beg, name_string.len,
                                  &active_origin_));
        previous_name_ = true;
        return (lexer_.getNextToken(MasterToken::STRING));
    }

    switch (initial_token.getType()) { // handle less common cases
    case MasterToken::END_OF_FILE:
        if (!popSource()) {
            return (initial_token);
        } else {
            // We try to read a token from the popped source
            // So retry the loop, but first, make sure the source
            // is at EOL
            eatUntilEOL(true);
            return (MasterToken(MasterToken::END_OF_LINE));
        }
    case MasterToken::END_OF_LINE:
        return (initial_token); // empty line
    case MasterToken::ERROR:
        // Error token here.
        isc_throw(InternalException, initial_token.getErrorText());
    default:
        // Some other token (what could that be?)
        isc_throw(InternalException, "Parser got confused (unexpected "
                  "token " << initial_token.getType() << ")");
    }
}

340 341
bool
MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
342 343 344
    if (count_limit == 0) {
        isc_throw(isc::InvalidParameter, "Count limit set to 0");
    }
345 346 347 348 349 350 351 352 353 354
    if (complete_) {
        isc_throw(isc::InvalidOperation,
                  "Trying to load when already loaded");
    }
    if (!initialized_) {
        pushSource(master_file_);
    }
    size_t count = 0;
    while (ok_ && count < count_limit) {
        try {
355 356 357 358 359
            const MasterToken next_token = handleInitialToken();
            if (next_token.getType() == MasterToken::END_OF_FILE) {
                return (true);  // we are done
            } else if (next_token.getType() == MasterToken::END_OF_LINE) {
                continue;       // nothing more to do in this line
360
            }
361 362 363
            // We are going to parse an RR, have known the owner name,
            // and are now seeing the next string token in the rest of the RR.
            assert(next_token.getType() == MasterToken::STRING);
364

365 366 367 368
            // TODO: Some more flexibility. We don't allow omitting
            // anything yet

            // The parameters
369
            const RRTTL ttl(next_token.getString());
370 371 372 373 374 375 376 377 378 379
            const RRClass rrclass(getString());
            const RRType rrtype(getString());

            // TODO: Some more validation?
            if (rrclass != zone_class_) {
                // It doesn't really matter much what type of exception
                // we throw, we catch it just below.
                isc_throw(isc::BadValue, "Class mismatch: " << rrclass <<
                          "vs. " << zone_class_);
            }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
380
            // TODO: Check if it is SOA, it should be at the origin.
381 382 383

            const rdata::RdataPtr data(rdata::createRdata(rrtype, rrclass,
                                                          lexer_,
384
                                                          &active_origin_,
385 386 387 388 389 390
                                                          options_,
                                                          callbacks_));
            // In case we get NULL, it means there was error creating
            // the Rdata. The errors should have been reported by
            // callbacks_ already. We need to decide if we want to continue
            // or not.
391
            if (data) {
392
                add_callback_(*last_name_, rrclass, rrtype, ttl, data);
393 394 395

                // Good, we loaded another one
                ++count;
396 397 398 399 400 401 402 403 404
            } else {
                seen_error_ = true;
                if (!many_errors_) {
                    ok_ = false;
                    complete_ = true;
                    // We don't have the exact error here, but it was reported
                    // by the error callback.
                    isc_throw(MasterLoaderError, "Invalid RR data");
                }
405 406 407 408 409 410 411 412 413 414 415 416
            }
        } catch (const MasterLoaderError&) {
            // This is a hack. We exclude the MasterLoaderError from the
            // below case. Once we restrict the below to some smaller
            // exception, we should remove this.
            throw;
        } catch (const isc::Exception& e) {
            // TODO: Once we do #2518, catch only the DNSTextError here,
            // not isc::Exception. The rest should be just simply
            // propagated.
            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
                        e.what());
417
            eatUntilEOL(false);
418 419 420 421 422 423
        }
    }
    // When there was a fatal error and ok is false, we say we are done.
    return (!ok_);
}

424 425
MasterLoader::MasterLoader(const char* master_file,
                           const Name& zone_origin,
426 427
                           const RRClass& zone_class,
                           const MasterLoaderCallbacks& callbacks,
428
                           const AddRRCallback& add_callback,
429 430
                           Options options)
{
431 432 433
    if (add_callback.empty()) {
        isc_throw(isc::InvalidParameter, "Empty add RR callback");
    }
434
    impl_ = new MasterLoaderImpl(master_file, zone_origin,
435
                                 zone_class, callbacks, add_callback, options);
436 437
}

438 439 440 441 442 443 444 445 446 447
MasterLoader::MasterLoader(std::istream& stream,
                           const Name& zone_origin,
                           const RRClass& zone_class,
                           const MasterLoaderCallbacks& callbacks,
                           const AddRRCallback& add_callback,
                           Options options)
{
    if (add_callback.empty()) {
        isc_throw(isc::InvalidParameter, "Empty add RR callback");
    }
448 449 450 451 452 453
    auto_ptr<MasterLoaderImpl> impl(new MasterLoaderImpl("", zone_origin,
                                                         zone_class, callbacks,
                                                         add_callback,
                                                         options));
    impl->pushStreamSource(stream);
    impl_ = impl.release();
454 455
}

456 457 458 459 460 461
MasterLoader::~MasterLoader() {
    delete impl_;
}

bool
MasterLoader::loadIncremental(size_t count_limit) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
462
    const bool result = impl_->loadIncremental(count_limit);
463 464
    impl_->complete_ = result;
    return (result);
465 466
}

467 468 469 470 471
bool
MasterLoader::loadedSucessfully() const {
    return (impl_->complete_ && !impl_->seen_error_);
}

472 473
} // end namespace dns
} // end namespace isc