pgsql_exchange.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. // Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <dhcpsrv/pgsql_exchange.h>
  7. #include <boost/lexical_cast.hpp>
  8. #include <iomanip>
  9. #include <sstream>
  10. #include <vector>
  11. namespace isc {
  12. namespace dhcp {
  13. const int PsqlBindArray::TEXT_FMT = 0;
  14. const int PsqlBindArray::BINARY_FMT = 1;
  15. const char* PsqlBindArray::TRUE_STR = "TRUE";
  16. const char* PsqlBindArray::FALSE_STR = "FALSE";
  17. void PsqlBindArray::add(const char* value) {
  18. if (!value) {
  19. isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL");
  20. }
  21. values_.push_back(value);
  22. lengths_.push_back(strlen(value));
  23. formats_.push_back(TEXT_FMT);
  24. }
  25. void PsqlBindArray::add(const std::string& value) {
  26. values_.push_back(value.c_str());
  27. lengths_.push_back(value.size());
  28. formats_.push_back(TEXT_FMT);
  29. }
  30. void PsqlBindArray::add(const std::vector<uint8_t>& data) {
  31. values_.push_back(reinterpret_cast<const char*>(&(data[0])));
  32. lengths_.push_back(data.size());
  33. formats_.push_back(BINARY_FMT);
  34. }
  35. void PsqlBindArray::add(const uint8_t* data, const size_t len) {
  36. if (!data) {
  37. isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL");
  38. }
  39. values_.push_back(reinterpret_cast<const char*>(&(data[0])));
  40. lengths_.push_back(len);
  41. formats_.push_back(BINARY_FMT);
  42. }
  43. void PsqlBindArray::add(const bool& value) {
  44. add(value ? TRUE_STR : FALSE_STR);
  45. }
  46. void PsqlBindArray::add(const uint8_t& byte) {
  47. // We static_cast to an unsigned int, otherwise lexcial_cast may to
  48. // treat byte as a character, which yields "" for unprintable values
  49. addTempString(boost::lexical_cast<std::string>
  50. (static_cast<unsigned int>(byte)));
  51. }
  52. void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) {
  53. if (addr.isV4()) {
  54. addTempString(boost::lexical_cast<std::string>
  55. (addr.toUint32()));
  56. } else {
  57. addTempString(addr.toText());
  58. }
  59. }
  60. void PsqlBindArray::addNull(const int format) {
  61. values_.push_back(NULL);
  62. lengths_.push_back(0);
  63. formats_.push_back(format);
  64. }
  65. /// @todo Eventually this could replace add(std::string&)? This would mean
  66. /// all bound strings would be internally copies rather than perhaps belonging
  67. /// to the originating object such as Host::hostname_. One the one hand it
  68. /// would make all strings handled one-way only, on the other hand it would
  69. /// mean duplicating strings where it isn't strictly necessary.
  70. void PsqlBindArray::addTempString(const std::string& str) {
  71. bound_strs_.push_back(ConstStringPtr(new std::string(str)));
  72. PsqlBindArray::add((bound_strs_.back())->c_str());
  73. }
  74. std::string PsqlBindArray::toText() const {
  75. std::ostringstream stream;
  76. for (int i = 0; i < values_.size(); ++i) {
  77. stream << i << " : ";
  78. if (formats_[i] == TEXT_FMT) {
  79. stream << "\"" << values_[i] << "\"" << std::endl;
  80. } else {
  81. const char *data = values_[i];
  82. if (lengths_[i] == 0) {
  83. stream << "empty" << std::endl;
  84. } else {
  85. stream << "0x";
  86. for (int x = 0; x < lengths_[i]; ++x) {
  87. stream << std::setfill('0') << std::setw(2)
  88. << std::setbase(16)
  89. << static_cast<unsigned int>(data[x]);
  90. }
  91. stream << std::endl;
  92. stream << std::setbase(10);
  93. }
  94. }
  95. }
  96. return (stream.str());
  97. }
  98. std::string
  99. PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
  100. struct tm tinfo;
  101. char buffer[20];
  102. localtime_r(&input_time, &tinfo);
  103. strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
  104. return (std::string(buffer));
  105. }
  106. std::string
  107. PgSqlExchange::convertToDatabaseTime(const time_t cltt,
  108. const uint32_t valid_lifetime) {
  109. // Calculate expiry time. Store it in the 64-bit value so as we can
  110. // detect overflows.
  111. int64_t expire_time_64 = static_cast<int64_t>(cltt)
  112. + static_cast<int64_t>(valid_lifetime);
  113. // It has been observed that the PostgreSQL doesn't deal well with the
  114. // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
  115. // beginning of the epoch (around year 2038). The value is often
  116. // stored in the database but it is invalid when read back (overflow?).
  117. // Hence, the maximum timestamp value is restricted here.
  118. if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
  119. isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
  120. }
  121. return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
  122. }
  123. time_t
  124. PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
  125. // Convert string time value to time_t
  126. time_t new_time;
  127. try {
  128. new_time = (boost::lexical_cast<time_t>(db_time_val));
  129. } catch (const std::exception& ex) {
  130. isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
  131. }
  132. return (new_time);
  133. }
  134. const char*
  135. PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
  136. const size_t col) {
  137. r.rowColCheck(row,col);
  138. const char* value = PQgetvalue(r, row, col);
  139. if (!value) {
  140. isc_throw(DbOperationError, "getRawColumnValue no data for :"
  141. << getColumnLabel(r, col) << " row:" << row);
  142. }
  143. return (value);
  144. }
  145. bool
  146. PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
  147. const size_t col) {
  148. r.rowColCheck(row,col);
  149. return (PQgetisnull(r, row, col));
  150. }
  151. void
  152. PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
  153. const size_t col, std::string& value) {
  154. value = getRawColumnValue(r, row, col);
  155. }
  156. void
  157. PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
  158. const size_t col, bool &value) {
  159. const char* data = getRawColumnValue(r, row, col);
  160. if (!strlen(data) || *data == 'f') {
  161. value = false;
  162. } else if (*data == 't') {
  163. value = true;
  164. } else {
  165. isc_throw(DbOperationError, "Invalid boolean data: " << data
  166. << " for: " << getColumnLabel(r, col) << " row:" << row
  167. << " : must be 't' or 'f'");
  168. }
  169. }
  170. void
  171. PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
  172. const size_t col, uint8_t &value) {
  173. const char* data = getRawColumnValue(r, row, col);
  174. try {
  175. // lexically casting as uint8_t doesn't convert from char
  176. // so we use uint16_t and implicitly convert.
  177. value = boost::lexical_cast<uint16_t>(data);
  178. } catch (const std::exception& ex) {
  179. isc_throw(DbOperationError, "Invalid uint8_t data: " << data
  180. << " for: " << getColumnLabel(r, col) << " row:" << row
  181. << " : " << ex.what());
  182. }
  183. }
  184. isc::asiolink::IOAddress
  185. PgSqlExchange::getIPv6Value(const PgSqlResult& r, const int row,
  186. const size_t col) {
  187. const char* data = getRawColumnValue(r, row, col);
  188. try {
  189. return (isc::asiolink::IOAddress(data));
  190. } catch (const std::exception& ex) {
  191. isc_throw(DbOperationError, "Cannot convert data: " << data
  192. << " for: " << getColumnLabel(r, col) << " row:" << row
  193. << " : " << ex.what());
  194. }
  195. }
  196. void
  197. PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
  198. const size_t col, uint8_t* buffer,
  199. const size_t buffer_size,
  200. size_t &bytes_converted) {
  201. // Returns converted bytes in a dynamically allocated buffer, and
  202. // sets bytes_converted.
  203. unsigned char* bytes = PQunescapeBytea((const unsigned char*)
  204. (getRawColumnValue(r, row, col)),
  205. &bytes_converted);
  206. // Unlikely it couldn't allocate it but you never know.
  207. if (!bytes) {
  208. isc_throw (DbOperationError, "PQunescapeBytea failed for:"
  209. << getColumnLabel(r, col) << " row:" << row);
  210. }
  211. // Make sure it's not larger than expected.
  212. if (bytes_converted > buffer_size) {
  213. // Free the allocated buffer first!
  214. PQfreemem(bytes);
  215. isc_throw (DbOperationError, "Converted data size: "
  216. << bytes_converted << " is too large for: "
  217. << getColumnLabel(r, col) << " row:" << row);
  218. }
  219. // Copy from the allocated buffer to caller's buffer the free up
  220. // the allocated buffer.
  221. memcpy(buffer, bytes, bytes_converted);
  222. PQfreemem(bytes);
  223. }
  224. std::string
  225. PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
  226. return (r.getColumnLabel(column));
  227. }
  228. std::string
  229. PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
  230. r.rowCheck(row);
  231. std::ostringstream stream;
  232. int columns = r.getCols();
  233. for (int col = 0; col < columns; ++col) {
  234. const char* val = getRawColumnValue(r, row, col);
  235. std::string name = r.getColumnLabel(col);
  236. int format = PQfformat(r, col);
  237. stream << col << " " << name << " : " ;
  238. if (format == PsqlBindArray::TEXT_FMT) {
  239. stream << "\"" << val << "\"" << std::endl;
  240. } else {
  241. const char *data = val;
  242. int length = PQfsize(r, col);
  243. if (length == 0) {
  244. stream << "empty" << std::endl;
  245. } else {
  246. stream << "0x";
  247. for (int i = 0; i < length; ++i) {
  248. stream << std::setfill('0') << std::setw(2)
  249. << std::setbase(16)
  250. << static_cast<unsigned int>(data[i]);
  251. }
  252. stream << std::endl;
  253. }
  254. }
  255. }
  256. return (stream.str());
  257. }
  258. }; // end of isc::dhcp namespace
  259. }; // end of isc namespace