dinit-client.h 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #include <cstdint>
  2. #include <cstring>
  3. #include "cpbuffer.h"
  4. // Client library for Dinit clients
  5. using handle_t = uint32_t;
  6. using cpbuffer_t = cpbuffer<1024>;
  7. class cp_read_exception
  8. {
  9. public:
  10. int errcode;
  11. cp_read_exception(int err) : errcode(err) { }
  12. };
  13. class cp_write_exception
  14. {
  15. public:
  16. int errcode;
  17. cp_write_exception(int err) : errcode(err) { }
  18. };
  19. class cp_old_client_exception
  20. {
  21. // no body
  22. };
  23. class cp_old_server_exception
  24. {
  25. // no body
  26. };
  27. // Fill a circular buffer from a file descriptor, until it contains at least _rlength_ bytes.
  28. // Throws cp_read_exception if the requested number of bytes cannot be read, with:
  29. // errcode = 0 if end of stream (remote end closed)
  30. // errcode = errno if another error occurred
  31. // Note that EINTR is ignored (i.e. the read will be re-tried).
  32. inline void fill_buffer_to(cpbuffer_t &buf, int fd, int rlength)
  33. {
  34. do {
  35. int r = buf.fill_to(fd, rlength);
  36. if (r == -1) {
  37. if (errno != EINTR) {
  38. throw cp_read_exception(errno);
  39. }
  40. }
  41. else if (r == 0) {
  42. throw cp_read_exception(0);
  43. }
  44. else {
  45. return;
  46. }
  47. }
  48. while (true);
  49. }
  50. // Wait for a reply packet, skipping over any information packets that are received in the meantime.
  51. inline void wait_for_reply(cpbuffer_t &rbuffer, int fd)
  52. {
  53. fill_buffer_to(rbuffer, fd, 1);
  54. while (rbuffer[0] >= 100) {
  55. // Information packet; discard.
  56. fill_buffer_to(rbuffer, fd, 2);
  57. int pktlen = (unsigned char) rbuffer[1];
  58. rbuffer.consume(1); // Consume one byte so we'll read one byte of the next packet
  59. fill_buffer_to(rbuffer, fd, pktlen);
  60. rbuffer.consume(pktlen - 1);
  61. }
  62. }
  63. // Wait for an info packet. If any other reply packet comes, throw a cp_read_exception.
  64. inline void wait_for_info(cpbuffer_t &rbuffer, int fd)
  65. {
  66. fill_buffer_to(rbuffer, fd, 2);
  67. if (rbuffer[0] < 100) {
  68. throw cp_read_exception(0);
  69. }
  70. int pktlen = (unsigned char) rbuffer[1];
  71. fill_buffer_to(rbuffer, fd, pktlen);
  72. }
  73. // Write *all* the requested buffer and re-try if necessary until
  74. // the buffer is written or an unrecoverable error occurs.
  75. inline int write_all(int fd, const void *buf, size_t count)
  76. {
  77. const char *cbuf = static_cast<const char *>(buf);
  78. int w = 0;
  79. while (count > 0) {
  80. int r = write(fd, cbuf, count);
  81. if (r == -1) {
  82. if (errno == EINTR) continue;
  83. return r;
  84. }
  85. w += r;
  86. cbuf += r;
  87. count -= r;
  88. }
  89. return w;
  90. }
  91. // Write all the requested buffer, and throw an exception on failure.
  92. inline void write_all_x(int fd, const void *buf, size_t count)
  93. {
  94. if (write_all(fd, buf, count) == -1) {
  95. throw cp_write_exception(errno);
  96. }
  97. }
  98. // Check the protocol version is compatible with the client.
  99. // minversion - minimum protocol version that client can speak
  100. // version - maximum protocol version that client can speak
  101. // rbuffer, fd - communication buffer and socket
  102. // returns: the actual protocol version
  103. // throws an exception on protocol mismatch or error.
  104. inline uint16_t check_protocol_version(int minversion, int version, cpbuffer_t &rbuffer, int fd)
  105. {
  106. constexpr int bufsize = 1;
  107. char buf[bufsize] = { DINIT_CP_QUERYVERSION };
  108. write_all_x(fd, buf, bufsize);
  109. wait_for_reply(rbuffer, fd);
  110. if (rbuffer[0] != DINIT_RP_CPVERSION) {
  111. throw cp_read_exception{0};
  112. }
  113. // DINIT_RP_CVERSION, (2 byte) minimum compatible version, (2 byte) actual version
  114. constexpr int rbufsize = 1 + 2 * sizeof(uint16_t);
  115. fill_buffer_to(rbuffer, fd, rbufsize);
  116. uint16_t rminversion;
  117. uint16_t cpversion;
  118. rbuffer.extract(reinterpret_cast<char *>(&rminversion), 1, sizeof(uint16_t));
  119. rbuffer.extract(reinterpret_cast<char *>(&cpversion), 1 + sizeof(uint16_t), sizeof(uint16_t));
  120. rbuffer.consume(rbufsize);
  121. if (rminversion > version) {
  122. // We are too old
  123. throw cp_old_client_exception();
  124. }
  125. if (cpversion < minversion) {
  126. // Server is too old
  127. throw cp_old_server_exception();
  128. }
  129. return cpversion;
  130. }