| 1 | // CODYlib -*- mode:c++ -*- |
| 2 | // Copyright (C) 2020 Nathan Sidwell, nathan@acm.org |
| 3 | // License: Apache v2.0 |
| 4 | |
| 5 | #ifndef CODY_HH |
| 6 | #define CODY_HH 1 |
| 7 | |
| 8 | // If the user specifies this as non-zero, it must be what we expect, |
| 9 | // generally only good for requesting no networking |
| 10 | #if !defined (CODY_NETWORKING) |
| 11 | // Have a known-good list of networking systems |
| 12 | #if defined (__unix__) || defined (__MACH__) |
| 13 | #define CODY_NETWORKING 1 |
| 14 | #else |
| 15 | #define CODY_NETWORKING 0 |
| 16 | #endif |
| 17 | #if 0 // For testing |
| 18 | #undef CODY_NETWORKING |
| 19 | #define CODY_NETWORKING 0 |
| 20 | #endif |
| 21 | #endif |
| 22 | |
| 23 | // C++ |
| 24 | #include <memory> |
| 25 | #include <string> |
| 26 | #include <vector> |
| 27 | // C |
| 28 | #include <cstddef> |
| 29 | // OS |
| 30 | #include <errno.h> |
| 31 | #include <sys/types.h> |
| 32 | #if CODY_NETWORKING |
| 33 | #include <sys/socket.h> |
| 34 | #endif |
| 35 | |
| 36 | namespace Cody { |
| 37 | |
| 38 | // Set version to 1, as this is completely incompatible with 0. |
| 39 | // Fortunately both versions 0 and 1 will recognize each other's HELLO |
| 40 | // messages sufficiently to error out |
| 41 | constexpr unsigned Version = 1; |
| 42 | |
| 43 | // FIXME: I guess we need a file-handle abstraction here |
| 44 | // Is windows DWORDPTR still?, or should it be FILE *? (ew). |
| 45 | |
| 46 | namespace Detail { |
| 47 | |
| 48 | // C++11 doesn't have utf8 character literals :( |
| 49 | |
| 50 | template<unsigned I> |
| 51 | constexpr char S2C (char const (&s)[I]) |
| 52 | { |
| 53 | static_assert (I == 2, "only single octet strings may be converted" ); |
| 54 | return s[0]; |
| 55 | } |
| 56 | |
| 57 | /// Internal buffering class. Used to concatenate outgoing messages |
| 58 | /// and Lex incoming ones. |
| 59 | class MessageBuffer |
| 60 | { |
| 61 | std::vector<char> buffer; ///< buffer holding the message |
| 62 | size_t lastBol = 0; ///< location of the most recent Beginning Of |
| 63 | ///< Line, or position we've readed when writing |
| 64 | |
| 65 | public: |
| 66 | MessageBuffer () = default; |
| 67 | ~MessageBuffer () = default; |
| 68 | MessageBuffer (MessageBuffer &&) = default; |
| 69 | MessageBuffer &operator= (MessageBuffer &&) = default; |
| 70 | |
| 71 | public: |
| 72 | /// |
| 73 | /// Finalize a buffer to be written. No more lines can be added to |
| 74 | /// the buffer. Use before a sequence of Write calls. |
| 75 | void PrepareToWrite () |
| 76 | { |
| 77 | buffer.push_back (x: u8"\n" [0]); |
| 78 | lastBol = 0; |
| 79 | } |
| 80 | /// |
| 81 | /// Prepare a buffer for reading. Use before a sequence of Read calls. |
| 82 | void PrepareToRead () |
| 83 | { |
| 84 | buffer.clear (); |
| 85 | lastBol = 0; |
| 86 | } |
| 87 | |
| 88 | public: |
| 89 | /// Begin a message line. Use before a sequence of Append and |
| 90 | /// related calls. |
| 91 | void BeginLine (); |
| 92 | /// End a message line. Use after a sequence of Append and related calls. |
| 93 | void EndLine () {} |
| 94 | |
| 95 | public: |
| 96 | /// Append a string to the current line. No whitespace is prepended |
| 97 | /// or appended. |
| 98 | /// |
| 99 | /// @param str the string to be written |
| 100 | /// @param maybe_quote indicate if there's a possibility the string |
| 101 | /// contains characters that need quoting. Defaults to false. |
| 102 | /// It is always safe to set |
| 103 | /// this true, but that causes an additional scan of the string. |
| 104 | /// @param len The length of the string. If not specified, strlen |
| 105 | /// is used to find the length. |
| 106 | void Append (char const *str, bool maybe_quote = false, |
| 107 | size_t len = ~size_t (0)); |
| 108 | |
| 109 | /// |
| 110 | /// Add whitespace word separator. Multiple adjacent whitespace is fine. |
| 111 | void Space () |
| 112 | { |
| 113 | Append (c: Detail::S2C(s: u8" " )); |
| 114 | } |
| 115 | |
| 116 | public: |
| 117 | /// Add a word as with Append, but prefixing whitespace to make a |
| 118 | /// separate word |
| 119 | void AppendWord (char const *str, bool maybe_quote = false, |
| 120 | size_t len = ~size_t (0)) |
| 121 | { |
| 122 | if (buffer.size () != lastBol) |
| 123 | Space (); |
| 124 | Append (str, maybe_quote, len); |
| 125 | } |
| 126 | /// Add a word as with AppendWord |
| 127 | /// @param str the string to append |
| 128 | /// @param maybe_quote string might need quoting, as for Append |
| 129 | void AppendWord (std::string const &str, bool maybe_quote = false) |
| 130 | { |
| 131 | AppendWord (str: str.data (), maybe_quote, len: str.size ()); |
| 132 | } |
| 133 | /// |
| 134 | /// Add an integral value, prepending a space. |
| 135 | void AppendInteger (unsigned u); |
| 136 | |
| 137 | private: |
| 138 | /// Append a literal character. |
| 139 | /// @param c character to append |
| 140 | void Append (char c); |
| 141 | |
| 142 | public: |
| 143 | /// Lex the next input line into a vector of words. |
| 144 | /// @param words filled with a vector of lexed strings |
| 145 | /// @result 0 if no errors, an errno value on lexxing error such as |
| 146 | /// there being no next line (ENOENT), or malformed quoting (EINVAL) |
| 147 | int Lex (std::vector<std::string> &words); |
| 148 | |
| 149 | public: |
| 150 | /// Append the most-recently lexxed line to a string. May be useful |
| 151 | /// in error messages. The unparsed line is appended -- before any |
| 152 | /// unquoting. |
| 153 | /// If we had c++17 string_view, we'd simply return a view of the |
| 154 | /// line, and leave it to the caller to do any concatenation. |
| 155 | /// @param l string to-which the lexxed line is appended. |
| 156 | void LexedLine (std::string &l); |
| 157 | |
| 158 | public: |
| 159 | /// Detect if we have reached the end of the input buffer. |
| 160 | /// I.e. there are no more lines to Lex |
| 161 | /// @result True if at end |
| 162 | bool IsAtEnd () const |
| 163 | { |
| 164 | return lastBol == buffer.size (); |
| 165 | } |
| 166 | |
| 167 | public: |
| 168 | /// Read from end point into a read buffer, as with read(2). This will |
| 169 | /// not block , unless FD is blocking, and there is nothing |
| 170 | /// immediately available. |
| 171 | /// @param fd file descriptor to read from. This may be a regular |
| 172 | /// file, pipe or socket. |
| 173 | /// @result on error returns errno. If end of file occurs, returns |
| 174 | /// -1. At end of message returns 0. If there is more needed |
| 175 | /// returns EAGAIN (or possibly EINTR). If the message is |
| 176 | /// malformed, returns EINVAL. |
| 177 | int Read (int fd) noexcept; |
| 178 | |
| 179 | public: |
| 180 | /// Write to an end point from a write buffer, as with write(2). As |
| 181 | /// with Read, this will not usually block. |
| 182 | /// @param fd file descriptor to write to. This may be a regular |
| 183 | /// file, pipe or socket. |
| 184 | /// @result on error returns errno. |
| 185 | /// At end of message returns 0. If there is more to write |
| 186 | /// returns EAGAIN (or possibly EINTR). |
| 187 | int Write (int fd) noexcept; |
| 188 | }; |
| 189 | |
| 190 | /// |
| 191 | /// Request codes. Perhaps this should be exposed? These are likely |
| 192 | /// useful to servers that queue requests. |
| 193 | enum RequestCode |
| 194 | { |
| 195 | RC_CONNECT, |
| 196 | RC_MODULE_REPO, |
| 197 | RC_MODULE_EXPORT, |
| 198 | RC_MODULE_IMPORT, |
| 199 | RC_MODULE_COMPILED, |
| 200 | RC_INCLUDE_TRANSLATE, |
| 201 | RC_HWM |
| 202 | }; |
| 203 | |
| 204 | /// Internal file descriptor tuple. It's used as an anonymous union member. |
| 205 | struct FD |
| 206 | { |
| 207 | int from; ///< Read from this FD |
| 208 | int to; ///< Write to this FD |
| 209 | }; |
| 210 | |
| 211 | } |
| 212 | |
| 213 | // Flags for various requests |
| 214 | enum class Flags : unsigned |
| 215 | { |
| 216 | None, |
| 217 | NameOnly = 1<<0, // Only querying for CMI names, not contents |
| 218 | }; |
| 219 | |
| 220 | inline Flags operator& (Flags a, Flags b) |
| 221 | { |
| 222 | return Flags (unsigned (a) & unsigned (b)); |
| 223 | } |
| 224 | inline Flags operator| (Flags a, Flags b) |
| 225 | { |
| 226 | return Flags (unsigned (a) | unsigned (b)); |
| 227 | } |
| 228 | |
| 229 | /// |
| 230 | /// Response data for a request. Returned by Client's request calls, |
| 231 | /// which return a single Packet. When the connection is Corked, the |
| 232 | /// Uncork call will return a vector of Packets. |
| 233 | class Packet |
| 234 | { |
| 235 | public: |
| 236 | /// |
| 237 | /// Packet is a variant structure. These are the possible content types. |
| 238 | enum Category { INTEGER, STRING, VECTOR}; |
| 239 | |
| 240 | private: |
| 241 | // std:variant is a C++17 thing, so we're doing this ourselves. |
| 242 | union |
| 243 | { |
| 244 | size_t integer; ///< Integral value |
| 245 | std::string string; ///< String value |
| 246 | std::vector<std::string> vector; ///< Vector of string value |
| 247 | }; |
| 248 | Category cat : 2; ///< Discriminator |
| 249 | |
| 250 | private: |
| 251 | unsigned short code = 0; ///< Packet type |
| 252 | unsigned short request = 0; |
| 253 | |
| 254 | public: |
| 255 | Packet (unsigned c, size_t i = 0) |
| 256 | : integer (i), cat (INTEGER), code (c) |
| 257 | { |
| 258 | } |
| 259 | Packet (unsigned c, std::string &&s) |
| 260 | : string (std::move (s)), cat (STRING), code (c) |
| 261 | { |
| 262 | } |
| 263 | Packet (unsigned c, std::string const &s) |
| 264 | : string (s), cat (STRING), code (c) |
| 265 | { |
| 266 | } |
| 267 | Packet (unsigned c, std::vector<std::string> &&v) |
| 268 | : vector (std::move (v)), cat (VECTOR), code (c) |
| 269 | { |
| 270 | } |
| 271 | // No non-move constructor from a vector. You should not be doing |
| 272 | // that. |
| 273 | |
| 274 | // Only move constructor and move assignment |
| 275 | Packet (Packet &&t) |
| 276 | { |
| 277 | Create (t: std::move (t)); |
| 278 | } |
| 279 | Packet &operator= (Packet &&t) |
| 280 | { |
| 281 | Destroy (); |
| 282 | Create (t: std::move (t)); |
| 283 | |
| 284 | return *this; |
| 285 | } |
| 286 | ~Packet () |
| 287 | { |
| 288 | Destroy (); |
| 289 | } |
| 290 | |
| 291 | private: |
| 292 | /// |
| 293 | /// Variant move creation from another packet |
| 294 | void Create (Packet &&t); |
| 295 | /// |
| 296 | /// Variant destruction |
| 297 | void Destroy (); |
| 298 | |
| 299 | public: |
| 300 | /// |
| 301 | /// Return the packet type |
| 302 | unsigned GetCode () const |
| 303 | { |
| 304 | return code; |
| 305 | } |
| 306 | /// |
| 307 | /// Return the packet type |
| 308 | unsigned GetRequest () const |
| 309 | { |
| 310 | return request; |
| 311 | } |
| 312 | void SetRequest (unsigned r) |
| 313 | { |
| 314 | request = r; |
| 315 | } |
| 316 | /// |
| 317 | /// Return the category of the packet's payload |
| 318 | Category GetCategory () const |
| 319 | { |
| 320 | return cat; |
| 321 | } |
| 322 | |
| 323 | public: |
| 324 | /// |
| 325 | /// Return an integral payload. Undefined if the category is not INTEGER |
| 326 | size_t GetInteger () const |
| 327 | { |
| 328 | return integer; |
| 329 | } |
| 330 | /// |
| 331 | /// Return (a reference to) a string payload. Undefined if the |
| 332 | /// category is not STRING |
| 333 | std::string const &GetString () const |
| 334 | { |
| 335 | return string; |
| 336 | } |
| 337 | std::string &GetString () |
| 338 | { |
| 339 | return string; |
| 340 | } |
| 341 | /// |
| 342 | /// Return (a reference to) a constant vector of strings payload. |
| 343 | /// Undefined if the category is not VECTOR |
| 344 | std::vector<std::string> const &GetVector () const |
| 345 | { |
| 346 | return vector; |
| 347 | } |
| 348 | /// |
| 349 | /// Return (a reference to) a non-conatant vector of strings payload. |
| 350 | /// Undefined if the category is not VECTOR |
| 351 | std::vector<std::string> &GetVector () |
| 352 | { |
| 353 | return vector; |
| 354 | } |
| 355 | }; |
| 356 | |
| 357 | class Server; |
| 358 | |
| 359 | /// |
| 360 | /// Client-side (compiler) object. |
| 361 | class Client |
| 362 | { |
| 363 | public: |
| 364 | /// Response packet codes |
| 365 | enum PacketCode |
| 366 | { |
| 367 | PC_CORKED, ///< Messages are corked |
| 368 | PC_CONNECT, ///< Packet is integer version |
| 369 | PC_ERROR, ///< Packet is error string |
| 370 | PC_OK, |
| 371 | PC_BOOL, |
| 372 | PC_PATHNAME |
| 373 | }; |
| 374 | |
| 375 | private: |
| 376 | Detail::MessageBuffer write; ///< Outgoing write buffer |
| 377 | Detail::MessageBuffer read; ///< Incoming read buffer |
| 378 | std::string corked; ///< Queued request tags |
| 379 | union |
| 380 | { |
| 381 | Detail::FD fd; ///< FDs connecting to server |
| 382 | Server *server; ///< Directly connected server |
| 383 | }; |
| 384 | bool is_direct = false; ///< Discriminator |
| 385 | bool is_connected = false; /// Connection handshake succesful |
| 386 | |
| 387 | private: |
| 388 | Client (); |
| 389 | public: |
| 390 | /// Direct connection constructor. |
| 391 | /// @param s Server to directly connect |
| 392 | Client (Server *s) |
| 393 | : Client () |
| 394 | { |
| 395 | is_direct = true; |
| 396 | server = s; |
| 397 | } |
| 398 | /// Communication connection constructor |
| 399 | /// @param from file descriptor to read from |
| 400 | /// @param to file descriptor to write to, defaults to from |
| 401 | Client (int from, int to = -1) |
| 402 | : Client () |
| 403 | { |
| 404 | fd.from = from; |
| 405 | fd.to = to < 0 ? from : to; |
| 406 | } |
| 407 | ~Client (); |
| 408 | // We have to provide our own move variants, because of the variant member. |
| 409 | Client (Client &&); |
| 410 | Client &operator= (Client &&); |
| 411 | |
| 412 | public: |
| 413 | /// |
| 414 | /// Direct connection predicate |
| 415 | bool IsDirect () const |
| 416 | { |
| 417 | return is_direct; |
| 418 | } |
| 419 | /// |
| 420 | /// Successful handshake predicate |
| 421 | bool IsConnected () const |
| 422 | { |
| 423 | return is_connected; |
| 424 | } |
| 425 | |
| 426 | public: |
| 427 | /// |
| 428 | /// Get the read FD |
| 429 | /// @result the FD to read from, -1 if a direct connection |
| 430 | int GetFDRead () const |
| 431 | { |
| 432 | return is_direct ? -1 : fd.from; |
| 433 | } |
| 434 | /// |
| 435 | /// Get the write FD |
| 436 | /// @result the FD to write to, -1 if a direct connection |
| 437 | int GetFDWrite () const |
| 438 | { |
| 439 | return is_direct ? -1 : fd.to; |
| 440 | } |
| 441 | /// |
| 442 | /// Get the directly-connected server |
| 443 | /// @result the server, or nullptr if a communication connection |
| 444 | Server *GetServer () const |
| 445 | { |
| 446 | return is_direct ? server : nullptr; |
| 447 | } |
| 448 | |
| 449 | public: |
| 450 | /// |
| 451 | /// Perform connection handshake. All othe requests will result in |
| 452 | /// errors, until handshake is succesful. |
| 453 | /// @param agent compiler identification |
| 454 | /// @param ident compilation identifiation (maybe nullptr) |
| 455 | /// @param alen length of agent string, if known |
| 456 | /// @param ilen length of ident string, if known |
| 457 | /// @result packet indicating success (or deferrment) of the |
| 458 | /// connection, payload is optional flags |
| 459 | Packet Connect (char const *agent, char const *ident, |
| 460 | size_t alen = ~size_t (0), size_t ilen = ~size_t (0)); |
| 461 | /// std::string wrapper for connection |
| 462 | /// @param agent compiler identification |
| 463 | /// @param ident compilation identification |
| 464 | Packet Connect (std::string const &agent, std::string const &ident) |
| 465 | { |
| 466 | return Connect (agent: agent.c_str (), ident: ident.c_str (), |
| 467 | alen: agent.size (), ilen: ident.size ()); |
| 468 | } |
| 469 | |
| 470 | public: |
| 471 | /// Request compiler module repository |
| 472 | /// @result packet indicating repo |
| 473 | Packet ModuleRepo (); |
| 474 | |
| 475 | public: |
| 476 | /// Inform of compilation of a named module interface or partition, |
| 477 | /// or a header unit |
| 478 | /// @param str module or header-unit |
| 479 | /// @param len name length, if known |
| 480 | /// @result CMI name (or deferrment/error) |
| 481 | Packet ModuleExport (char const *str, Flags flags, size_t len = ~size_t (0)); |
| 482 | |
| 483 | Packet ModuleExport (char const *str) |
| 484 | { |
| 485 | return ModuleExport (str, flags: Flags::None, len: ~size_t (0)); |
| 486 | } |
| 487 | Packet ModuleExport (std::string const &s, Flags flags = Flags::None) |
| 488 | { |
| 489 | return ModuleExport (str: s.c_str (), flags, len: s.size ()); |
| 490 | } |
| 491 | |
| 492 | public: |
| 493 | /// Importation of a module, partition or header-unit |
| 494 | /// @param str module or header-unit |
| 495 | /// @param len name length, if known |
| 496 | /// @result CMI name (or deferrment/error) |
| 497 | Packet ModuleImport (char const *str, Flags flags, size_t len = ~size_t (0)); |
| 498 | |
| 499 | Packet ModuleImport (char const *str) |
| 500 | { |
| 501 | return ModuleImport (str, flags: Flags::None, len: ~size_t (0)); |
| 502 | } |
| 503 | Packet ModuleImport (std::string const &s, Flags flags = Flags::None) |
| 504 | { |
| 505 | return ModuleImport (str: s.c_str (), flags, len: s.size ()); |
| 506 | } |
| 507 | |
| 508 | public: |
| 509 | /// Successful compilation of a module interface, partition or |
| 510 | /// header-unit. Must have been preceeded by a ModuleExport |
| 511 | /// request. |
| 512 | /// @param str module or header-unit |
| 513 | /// @param len name length, if known |
| 514 | /// @result OK (or deferment/error) |
| 515 | Packet ModuleCompiled (char const *str, Flags flags, size_t len = ~size_t (0)); |
| 516 | |
| 517 | Packet ModuleCompiled (char const *str) |
| 518 | { |
| 519 | return ModuleCompiled (str, flags: Flags::None, len: ~size_t (0)); |
| 520 | } |
| 521 | Packet ModuleCompiled (std::string const &s, Flags flags = Flags::None) |
| 522 | { |
| 523 | return ModuleCompiled (str: s.c_str (), flags, len: s.size ()); |
| 524 | } |
| 525 | |
| 526 | /// Include translation query. |
| 527 | /// @param str header unit name |
| 528 | /// @param len name length, if known |
| 529 | /// @result Packet indicating include translation boolean, or CMI |
| 530 | /// name (or deferment/error) |
| 531 | Packet IncludeTranslate (char const *str, Flags flags, |
| 532 | size_t len = ~size_t (0)); |
| 533 | |
| 534 | Packet IncludeTranslate (char const *str) |
| 535 | { |
| 536 | return IncludeTranslate (str, flags: Flags::None, len: ~size_t (0)); |
| 537 | } |
| 538 | Packet IncludeTranslate (std::string const &s, Flags flags = Flags::None) |
| 539 | { |
| 540 | return IncludeTranslate (str: s.c_str (), flags, len: s.size ()); |
| 541 | } |
| 542 | |
| 543 | public: |
| 544 | /// Cork the connection. All requests are queued up. Each request |
| 545 | /// call will return a PC_CORKED packet. |
| 546 | void Cork (); |
| 547 | |
| 548 | /// Uncork the connection. All queued requests are sent to the |
| 549 | /// server, and a block of responses waited for. |
| 550 | /// @result A vector of packets, containing the in-order responses to the |
| 551 | /// queued requests. |
| 552 | std::vector<Packet> Uncork (); |
| 553 | /// |
| 554 | /// Indicate corkedness of connection |
| 555 | bool IsCorked () const |
| 556 | { |
| 557 | return !corked.empty (); |
| 558 | } |
| 559 | |
| 560 | private: |
| 561 | Packet ProcessResponse (std::vector<std::string> &, unsigned code, |
| 562 | bool isLast); |
| 563 | Packet MaybeRequest (unsigned code); |
| 564 | int CommunicateWithServer (); |
| 565 | }; |
| 566 | |
| 567 | /// This server-side class is used to resolve requests from one or |
| 568 | /// more clients. You are expected to derive from it and override the |
| 569 | /// virtual functions it provides. The connection resolver may return |
| 570 | /// a different resolved object to service the remainder of the |
| 571 | /// connection -- for instance depending on the compiler that is |
| 572 | /// making the requests. |
| 573 | class Resolver |
| 574 | { |
| 575 | public: |
| 576 | Resolver () = default; |
| 577 | virtual ~Resolver (); |
| 578 | |
| 579 | protected: |
| 580 | /// Mapping from a module or header-unit name to a CMI file name. |
| 581 | /// @param module module name |
| 582 | /// @result CMI name |
| 583 | virtual std::string GetCMIName (std::string const &module); |
| 584 | |
| 585 | /// Return the CMI file suffix to use |
| 586 | /// @result CMI suffix, a statically allocated string |
| 587 | virtual char const *GetCMISuffix (); |
| 588 | |
| 589 | public: |
| 590 | /// When the requests of a directly-connected server are processed, |
| 591 | /// we may want to wait for the requests to complete (for instance a |
| 592 | /// set of subjobs). |
| 593 | /// @param s directly connected server. |
| 594 | virtual void WaitUntilReady (Server *s); |
| 595 | |
| 596 | public: |
| 597 | /// Provide an error response. |
| 598 | /// @param s the server to provide the response to. |
| 599 | /// @param msg the error message |
| 600 | virtual void ErrorResponse (Server *s, std::string &&msg); |
| 601 | |
| 602 | public: |
| 603 | /// Connection handshake. Provide response to server and return new |
| 604 | /// (or current) resolver, or nullptr. |
| 605 | /// @param s server to provide response to |
| 606 | /// @param version the client's version number |
| 607 | /// @param agent the client agent (compiler identification) |
| 608 | /// @param ident the compilation identification (may be empty) |
| 609 | /// @result nullptr in the case of an error. An error response will |
| 610 | /// be sent. If handing off to another resolver, return that, |
| 611 | /// otherwise this |
| 612 | virtual Resolver *ConnectRequest (Server *s, unsigned version, |
| 613 | std::string &agent, std::string &ident); |
| 614 | |
| 615 | public: |
| 616 | // return 0 on ok, ERRNO on failure, -1 on unspecific error |
| 617 | virtual int ModuleRepoRequest (Server *s); |
| 618 | |
| 619 | virtual int ModuleExportRequest (Server *s, Flags flags, |
| 620 | std::string &module); |
| 621 | virtual int ModuleImportRequest (Server *s, Flags flags, |
| 622 | std::string &module); |
| 623 | virtual int ModuleCompiledRequest (Server *s, Flags flags, |
| 624 | std::string &module); |
| 625 | virtual int IncludeTranslateRequest (Server *s, Flags flags, |
| 626 | std::string &include); |
| 627 | }; |
| 628 | |
| 629 | |
| 630 | /// This server-side (build system) class handles a single connection |
| 631 | /// to a client. It has 3 states, READING:accumulating a message |
| 632 | /// block froma client, WRITING:writing a message block to a client |
| 633 | /// and PROCESSING:resolving requests. If the server does not spawn |
| 634 | /// jobs to build needed artifacts, the PROCESSING state will be brief. |
| 635 | class Server |
| 636 | { |
| 637 | public: |
| 638 | enum Direction |
| 639 | { |
| 640 | READING, // Server is waiting for completion of a (set of) |
| 641 | // requests from client. The next state will be PROCESSING. |
| 642 | WRITING, // Server is writing a (set of) responses to client. |
| 643 | // The next state will be READING. |
| 644 | PROCESSING // Server is processing client request(s). The next |
| 645 | // state will be WRITING. |
| 646 | }; |
| 647 | |
| 648 | private: |
| 649 | Detail::MessageBuffer write; |
| 650 | Detail::MessageBuffer read; |
| 651 | Resolver *resolver; |
| 652 | Detail::FD fd; |
| 653 | bool is_connected = false; |
| 654 | Direction direction : 2; |
| 655 | |
| 656 | public: |
| 657 | Server (Resolver *r); |
| 658 | Server (Resolver *r, int from, int to = -1) |
| 659 | : Server (r) |
| 660 | { |
| 661 | fd.from = from; |
| 662 | fd.to = to >= 0 ? to : from; |
| 663 | } |
| 664 | ~Server (); |
| 665 | Server (Server &&); |
| 666 | Server &operator= (Server &&); |
| 667 | |
| 668 | public: |
| 669 | bool IsConnected () const |
| 670 | { |
| 671 | return is_connected; |
| 672 | } |
| 673 | |
| 674 | public: |
| 675 | void SetDirection (Direction d) |
| 676 | { |
| 677 | direction = d; |
| 678 | } |
| 679 | |
| 680 | public: |
| 681 | Direction GetDirection () const |
| 682 | { |
| 683 | return direction; |
| 684 | } |
| 685 | int GetFDRead () const |
| 686 | { |
| 687 | return fd.from; |
| 688 | } |
| 689 | int GetFDWrite () const |
| 690 | { |
| 691 | return fd.to; |
| 692 | } |
| 693 | Resolver *GetResolver () const |
| 694 | { |
| 695 | return resolver; |
| 696 | } |
| 697 | |
| 698 | public: |
| 699 | /// Process requests from a directly-connected client. This is a |
| 700 | /// small wrapper around ProcessRequests, with some buffer swapping |
| 701 | /// for communication. It is expected that such processessing is |
| 702 | /// immediate. |
| 703 | /// @param from message block from client |
| 704 | /// @param to message block to client |
| 705 | void DirectProcess (Detail::MessageBuffer &from, Detail::MessageBuffer &to); |
| 706 | |
| 707 | public: |
| 708 | /// Process the messages queued in the read buffer. We enter the |
| 709 | /// PROCESSING state, and each message line causes various resolver |
| 710 | /// methods to be called. Once processed, the server may need to |
| 711 | /// wait for all the requests to be ready, or it may be able to |
| 712 | /// immediately write responses back. |
| 713 | void ProcessRequests (); |
| 714 | |
| 715 | public: |
| 716 | /// Accumulate an error response. |
| 717 | /// @param error the error message to encode |
| 718 | /// @param elen length of error, if known |
| 719 | void ErrorResponse (char const *error, size_t elen = ~size_t (0)); |
| 720 | void ErrorResponse (std::string const &error) |
| 721 | { |
| 722 | ErrorResponse (error: error.data (), elen: error.size ()); |
| 723 | } |
| 724 | |
| 725 | /// Accumulate an OK response |
| 726 | void OKResponse (); |
| 727 | |
| 728 | /// Accumulate a boolean response |
| 729 | void BoolResponse (bool); |
| 730 | |
| 731 | /// Accumulate a pathname response |
| 732 | /// @param path (may be nullptr, or empty) |
| 733 | /// @param rlen length, if known |
| 734 | void PathnameResponse (char const *path, size_t plen = ~size_t (0)); |
| 735 | void PathnameResponse (std::string const &path) |
| 736 | { |
| 737 | PathnameResponse (path: path.data (), plen: path.size ()); |
| 738 | } |
| 739 | |
| 740 | public: |
| 741 | /// Accumulate a (successful) connection response |
| 742 | /// @param agent the server-side agent |
| 743 | /// @param alen agent length, if known |
| 744 | void ConnectResponse (char const *agent, size_t alen = ~size_t (0)); |
| 745 | void ConnectResponse (std::string const &agent) |
| 746 | { |
| 747 | ConnectResponse (agent: agent.data (), alen: agent.size ()); |
| 748 | } |
| 749 | |
| 750 | public: |
| 751 | /// Write message block to client. Semantics as for |
| 752 | /// MessageBuffer::Write. |
| 753 | /// @result errno or completion (0). |
| 754 | int Write () |
| 755 | { |
| 756 | return write.Write (fd: fd.to); |
| 757 | } |
| 758 | /// Initialize for writing a message block. All responses to the |
| 759 | /// incomping message block must be complete Enters WRITING state. |
| 760 | void PrepareToWrite () |
| 761 | { |
| 762 | write.PrepareToWrite (); |
| 763 | direction = WRITING; |
| 764 | } |
| 765 | |
| 766 | public: |
| 767 | /// Read message block from client. Semantics as for |
| 768 | /// MessageBuffer::Read. |
| 769 | /// @result errno, eof (-1) or completion (0) |
| 770 | int Read () |
| 771 | { |
| 772 | return read.Read (fd: fd.from); |
| 773 | } |
| 774 | /// Initialize for reading a message block. Enters READING state. |
| 775 | void PrepareToRead () |
| 776 | { |
| 777 | read.PrepareToRead (); |
| 778 | direction = READING; |
| 779 | } |
| 780 | }; |
| 781 | |
| 782 | // Helper network stuff |
| 783 | |
| 784 | #if CODY_NETWORKING |
| 785 | // Socket with specific address |
| 786 | int OpenSocket (char const **, sockaddr const *sock, socklen_t len); |
| 787 | int ListenSocket (char const **, sockaddr const *sock, socklen_t len, |
| 788 | unsigned backlog); |
| 789 | |
| 790 | // Local domain socket (eg AF_UNIX) |
| 791 | int OpenLocal (char const **, char const *name); |
| 792 | int ListenLocal (char const **, char const *name, unsigned backlog = 0); |
| 793 | |
| 794 | // ipv6 socket |
| 795 | int OpenInet6 (char const **e, char const *name, int port); |
| 796 | int ListenInet6 (char const **, char const *name, int port, |
| 797 | unsigned backlog = 0); |
| 798 | #endif |
| 799 | |
| 800 | // FIXME: Mapping file utilities? |
| 801 | |
| 802 | } |
| 803 | |
| 804 | #endif // CODY_HH |
| 805 | |