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