bill_communication.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. /*
  2. * Copyright (c) 2019 Clementine Computing LLC.
  3. *
  4. * This file is part of PopuFare.
  5. *
  6. * PopuFare is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * PopuFare is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with PopuFare. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. */
  20. #include <sys/socket.h>
  21. #include <sys/types.h>
  22. #include <sys/stat.h>
  23. #include <sys/un.h>
  24. #include <netinet/in.h>
  25. #include <arpa/inet.h>
  26. #include <stdio.h>
  27. #include <stdlib.h>
  28. #include <unistd.h>
  29. #include <errno.h>
  30. #include <poll.h>
  31. #include <time.h>
  32. #include "../common/common_defs.h"
  33. #include "../commhub/commhub.h"
  34. #include "../commhub/client_utils.h"
  35. #include "billdb.h"
  36. //----------GLOBAL STATE VARIABLES
  37. int flush_in_progress = 0; //This flag is used to tell if there is a flush in progress
  38. time_t last_sync_attempt = 0; //Time of the last connection attempt
  39. int commhub_fd = -1; //File descriptor of our connection to the comm hub
  40. int server_fd = -1; //File descriptor of our connection to the sync server
  41. bill_status real_bill_status = {0};
  42. time_t last_watermark_warning = 0; //The time of our last high watermark warning for the driver when space is low
  43. //This function attempts to connect to the bill server...
  44. int connect_to_bill_server()
  45. {
  46. int fd;
  47. int retval;
  48. struct sockaddr_in addr;
  49. fd = socket(PF_INET, SOCK_STREAM, 0);
  50. if(fd < 0)
  51. return -1;
  52. addr.sin_family = AF_INET;
  53. addr.sin_port = htons(BILL_SERVER_PORT);
  54. inet_aton(BILL_SERVER_IP, &addr.sin_addr);
  55. retval = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
  56. if(retval < 0)
  57. {
  58. close(fd);
  59. return -2;
  60. }
  61. return fd;
  62. }
  63. int handle_watermark_warnings(billdb_context *ctx, int force)
  64. {
  65. time_t now = time(NULL);
  66. struct message_record error_message;
  67. if(!ctx)
  68. return -1;
  69. if(ctx->num_free_records < BILLING_CRITICAL_THRESHOLD) //if we have a critical high watermark condition
  70. {
  71. //and we are either allowed to force a message or it is time to deliver the next one
  72. if(force || ((now - last_watermark_warning) >= BILLING_CRITICAL_FREQUENCY))
  73. {
  74. if(commhub_fd >= 0)
  75. {
  76. format_log_message(&error_message, LOGLEVEL_ERROR, "LOG CRITICAL: Call Dispatch");
  77. send_message(commhub_fd, &error_message);
  78. format_driver_message(&error_message, LOGLEVEL_ERROR, "LOG CRITICAL: Call Dispatch");
  79. send_message(commhub_fd, &error_message);
  80. last_watermark_warning = now;
  81. }
  82. else
  83. {
  84. return -1;
  85. }
  86. }
  87. }
  88. else if(ctx->num_free_records < BILLING_HIGH_THRESHOLD) //if we have a high watermark condition
  89. {
  90. //and we are either allowed to force a message or it is time to deliver the next one
  91. if(force || ((now - last_watermark_warning) >= BILLING_HIGH_FREQUENCY))
  92. {
  93. if(commhub_fd >= 0)
  94. {
  95. format_log_message(&error_message, LOGLEVEL_ERROR, "LOG HALF FULL: Call Dispatch");
  96. send_message(commhub_fd, &error_message);
  97. format_driver_message(&error_message, LOGLEVEL_ERROR, "LOG HALF FULL: Call Dispatch");
  98. send_message(commhub_fd, &error_message);
  99. last_watermark_warning = now;
  100. }
  101. else
  102. {
  103. return -1;
  104. }
  105. }
  106. }
  107. return 0;
  108. }
  109. int send_next_log(int fd, billdb_context *ctx, int next_idx)
  110. {
  111. char buffer[BILLING_LINE_SIZE + 2] = {0};
  112. int n, i, ret;
  113. if(!ctx)
  114. return 0;
  115. if(!ctx->bills)
  116. return 0;
  117. if(next_idx < 0)
  118. return 0;
  119. //Grab the billing log lone
  120. strncpy(buffer, ctx->bills[next_idx].data, BILLING_LINE_SIZE);
  121. n = strlen(buffer);
  122. //if it's zero length, that means that we've struck it from the record
  123. //already (it was ack'd on this same run through the poll loop).
  124. if(n == 0)
  125. {
  126. return 0;
  127. }
  128. //Add a newline
  129. buffer[n++] = '\n';
  130. buffer[n] = '\0';
  131. i = 0;
  132. //send it!
  133. while(i < n)
  134. {
  135. ret = send(fd, buffer + i, n - i, 0);
  136. if( ret <= 0 )
  137. return -1;
  138. i += ret;
  139. }
  140. //And mark it as sent now, so that we will only revisit it once the timer has run out...
  141. ctx->last_tx[next_idx] = time(NULL);
  142. return 1;
  143. }
  144. int handle_bill_reply(char *line, billdb_context *ctx)
  145. {
  146. char buffer[LINE_BUFFER_SIZE];
  147. int input_idx = 0;
  148. int eol = 0;
  149. int retval;
  150. //Extract the first tab-delimited field from the input line...
  151. input_idx += get_field(buffer, line + input_idx, sizeof(buffer), &eol);
  152. //If that field is blank, then we ignore this line
  153. if( buffer[0] == '\0' )
  154. {
  155. return 0;
  156. }
  157. if( !strcasecmp(buffer, "ACK") )
  158. {
  159. if( eol )
  160. {
  161. // printf("ACK: Premature end of line!\n");
  162. return -1;
  163. }
  164. //Get the next field (this should be a checksum...)
  165. input_idx += get_field(buffer, line + input_idx, sizeof(buffer), &eol);
  166. real_bill_status.last_ack_time = time(NULL); //update ack time
  167. retval = clear_billing_entry(ctx, buffer); //clear the billing entry
  168. real_bill_status.unsynced_messages = NUM_BILLING_ENTRIES - ctx->num_free_records; //update buffer count
  169. return retval;
  170. }
  171. else if( !strcasecmp(buffer, "DUP") )
  172. {
  173. if( eol )
  174. {
  175. // printf("DUP: Premature end of line!\n");
  176. return -1;
  177. }
  178. //Get the next field (this should be a checksum...)
  179. input_idx += get_field(buffer, line + input_idx, sizeof(buffer), &eol);
  180. real_bill_status.last_ack_time = time(NULL); //update ack time
  181. retval = clear_billing_entry(ctx, buffer); //clear the billing entry
  182. real_bill_status.unsynced_messages = NUM_BILLING_ENTRIES - ctx->num_free_records; //update buffer count
  183. return retval;
  184. }
  185. else if( !strcasecmp(buffer, "IGN") )
  186. {
  187. if( eol )
  188. {
  189. // printf("IGN: Premature end of line!\n");
  190. return -1;
  191. }
  192. //Get the next field (this should be a checksum...)
  193. input_idx += get_field(buffer, line + input_idx, sizeof(buffer), &eol);
  194. real_bill_status.last_ack_time = time(NULL); //update ack time
  195. retval = clear_billing_entry(ctx, buffer); //clear the billing entry
  196. real_bill_status.unsynced_messages = NUM_BILLING_ENTRIES - ctx->num_free_records; //update buffer count
  197. return retval;
  198. }
  199. else
  200. {
  201. fprintf(stderr, "Unknown command \"%s\"\n", buffer);
  202. }
  203. return 0;
  204. }
  205. void maintain_ipc_hub_connect(char *progname)
  206. {
  207. struct message_record outgoing_msg;
  208. if(commhub_fd < 0) //if we have no connection to the communication hub
  209. {
  210. commhub_fd = connect_to_message_server(progname); //try and get one
  211. if(commhub_fd >= 0) //if it worked
  212. {
  213. //Subscribe to the basics
  214. subscribe_to_default_messages(commhub_fd);
  215. //Subscribe to the command mailboxes we act on
  216. prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_BILLING_LOG, strlen(MAILBOX_BILLING_LOG));
  217. send_message(commhub_fd,&outgoing_msg);
  218. prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PADDLE_ACK, strlen(MAILBOX_PADDLE_ACK));
  219. send_message(commhub_fd,&outgoing_msg);
  220. //Request updated status information...
  221. prepare_message(&outgoing_msg, MAILBOX_STATUS_REQUEST, "", 0);
  222. send_message(commhub_fd,&outgoing_msg);
  223. }
  224. }
  225. }
  226. int send_bill_update(int force)
  227. {
  228. struct message_record outgoing_msg;
  229. //if EITHER the force flag is set, OR our record differs from the last one on file from the IPC hub...
  230. if( force || memcmp(&bill_stat, &real_bill_status, sizeof(real_bill_status)) )
  231. {
  232. if(commhub_fd >= 0)
  233. {
  234. //prepare and send it
  235. prepare_message(&outgoing_msg, MAILBOX_BILL_STATUS, &real_bill_status, sizeof(real_bill_status));
  236. send_message(commhub_fd,&outgoing_msg);
  237. }
  238. return 1;
  239. }
  240. return 0;
  241. }
  242. message_callback_return handle_billing_log_message(struct message_record *msg, void *param)
  243. {
  244. billdb_context *ctx = (billdb_context *)param;
  245. int retval = 0;
  246. int ignore = 0;
  247. // If this is a diagnostic log message, the first character will be a priority
  248. // code, otherwise for a billing log message it will be numeric.
  249. char priority = ((char *)msg->payload)[0];
  250. switch(priority)
  251. {
  252. case LOGLEVEL_DEBUG: //if this is a diagnostic message with a priority of LOGLEVEL_DEBUG
  253. //and there are fewer than REJECT_DIAG_DEBUG_THRESHOLD free buffer slots
  254. if(ctx->num_free_records < REJECT_DIAG_DEBUG_THRESHOLD)
  255. {
  256. ignore = 1; //then for safety, ignore this message
  257. }
  258. else
  259. {
  260. ignore = 0; //otherwise, if we have plenty of space, store it
  261. }
  262. break;
  263. case LOGLEVEL_WARN: //if this is a diagnostic message with a priority of LOGLEVEL_WARN
  264. //and there are fewer than REJECT_DIAG_WARN_THRESHOLD free buffer slots
  265. if(ctx->num_free_records < REJECT_DIAG_WARN_THRESHOLD)
  266. {
  267. ignore = 1; //then for safety, ignore this message
  268. }
  269. else
  270. {
  271. ignore = 0; //otherwise, if we have plenty of space, store it
  272. }
  273. break;
  274. case LOGLEVEL_ERROR: //if this is a diagnostic message with a priority of LOGLEVEL_ERROR
  275. //and there are fewer than REJECT_DIAG_ERROR_THRESHOLD free buffer slots
  276. if(ctx->num_free_records < REJECT_DIAG_ERROR_THRESHOLD)
  277. {
  278. ignore = 1; //then for safety, ignore this message
  279. }
  280. else
  281. {
  282. ignore = 0; //otherwise, if we have plenty of space, store it
  283. }
  284. break;
  285. default: //If this message does not start with a known diagnostic priority code, it is a billing message
  286. ignore = 0; //and therefore can never be ignored.
  287. break;
  288. }
  289. if(ignore) //If we've decided to ignore the message
  290. {
  291. // This means we are ignoring a message. This should only happen when buffer space
  292. // is scarce and the message is a diagnostic log message whith a priority that does not
  293. // justify potentially missing a billing entry later.
  294. }
  295. else //Otherwise, we want to process it as normal.
  296. {
  297. retval = add_billing_entry(ctx, (char *)msg->payload); //Attempt to add the message to the billing log
  298. switch(retval) //Test to see if that worked
  299. {
  300. case FAIL_FULL: //if the add failed
  301. case FAIL_MEM:
  302. if(commhub_fd >= 0) //and we can talk to the commhub
  303. {
  304. struct message_record error_message;
  305. format_driver_message(&error_message, LOGLEVEL_ERROR, "LOG FULL: Call Dispatch"); //notify the driver
  306. send_message(commhub_fd, &error_message);
  307. }
  308. break;
  309. default: //Otherwise, if the add succeded
  310. real_bill_status.unsynced_messages = NUM_BILLING_ENTRIES - ctx->num_free_records; //update buffer count
  311. break;
  312. }
  313. send_bill_update(0); //In either case, sent a status update if needed.
  314. }
  315. return MESSAGE_HANDLED_CONT;
  316. }
  317. message_callback_return handle_status_request_message(struct message_record *msg, void *param)
  318. {
  319. billdb_context *ctx = (billdb_context *)param;
  320. real_bill_status.unsynced_messages = NUM_BILLING_ENTRIES - ctx->num_free_records; //update buffer count
  321. //force a billing log status update if one is requested by a newly subscribing module
  322. send_bill_update(1);
  323. return MESSAGE_HANDLED_CONT;
  324. }
  325. int paddle_ack_flag = 0; //a variable to track if a paddle selection success has occurred (a driver has logged in)
  326. //this callback catches paddle selection acknowledge messages on the MAILBOX_PADDLE_ACK mailing list and
  327. //test to see if they report success, if so it sets the above flag so that we have the opportunity to warn a
  328. //newly logged in driver about a high-water-mark condition.
  329. message_callback_return handle_paddle_ack(struct message_record *msg, void *param)
  330. {
  331. set_paddle_req *payload = (set_paddle_req *)msg->payload;
  332. if(payload)
  333. {
  334. paddle_ack_flag = (payload->request == payload->result);
  335. }
  336. return MESSAGE_HANDLED_CONT;
  337. }
  338. int main(int argc, char **argv)
  339. {
  340. struct pollfd fds[2];
  341. int nfds = 0;
  342. int poll_return = 0;
  343. int read_return = 0;
  344. int next_sync_idx = -1;
  345. int i;
  346. struct message_record incoming_msg;
  347. struct message_record outgoing_msg;
  348. char input_line[LINE_BUFFER_SIZE] = {0};
  349. int input_idx = 0;
  350. int checked_idx = 0;
  351. #ifdef DEBUG_PRINT
  352. long long int _usec_now, _usec_prv, _usec_del;
  353. _usec_del = 60000000;
  354. _usec_now = get_usec_time();
  355. _usec_prv = _usec_now;
  356. #endif
  357. billdb_context ctx = {0};
  358. //------------------
  359. configure_signal_handlers(argv[0]);
  360. maintain_ipc_hub_connect(argv[0]);
  361. register_system_status_callbacks();
  362. //register our module-specific callback
  363. register_dispatch_callback(MAILBOX_BILLING_LOG, CALLBACK_USER(1), handle_billing_log_message, &ctx);
  364. register_dispatch_callback(MAILBOX_STATUS_REQUEST, CALLBACK_USER(2), handle_status_request_message, &ctx);
  365. register_dispatch_callback(MAILBOX_PADDLE_ACK, CALLBACK_USER(3), handle_paddle_ack, NULL);
  366. read_return = attach_to_billdb(&ctx);
  367. if( read_return < 0 )
  368. {
  369. fprintf(stdout, "Database is missing or corrupt. Attempting to format new database.\n");
  370. read_return = format_new_billdb();
  371. if( read_return < 0 )
  372. {
  373. fprintf(stderr, "Database cannot be created. Aborting!\n");
  374. return -1;
  375. }
  376. else
  377. {
  378. read_return = attach_to_billdb(&ctx);
  379. if( read_return < 0 )
  380. {
  381. fprintf(stderr, "New database is ALSO missing or corrupt. Aborting.\n");
  382. return -1;
  383. }
  384. }
  385. }
  386. while( exit_request_status == EXIT_REQUEST_NONE )
  387. {
  388. time_t now = time(NULL);
  389. RESET_WATCHDOG();
  390. #ifdef DEBUG_PRINT
  391. _usec_now = get_usec_time();
  392. if ((_usec_now - _usec_prv) > _usec_del) {
  393. printf("[%lli] billdb: heartbeat\n", get_usec_time());
  394. _usec_prv = _usec_now;
  395. }
  396. #endif
  397. maintain_ipc_hub_connect(argv[0]);
  398. if(server_fd < 0) //If we don't have a connection to the sync server...
  399. {
  400. if( (now - last_sync_attempt) > DEFAULT_CONNECT_RETRY ) //See if it is time to try again
  401. {
  402. if( tunnel_is_up() ) //and if the tunnel thinks it is up
  403. {
  404. server_fd = connect_to_bill_server(); //if so, try again...
  405. if(server_fd >= 0) //if it worked
  406. {
  407. input_idx = 0; //reset our buffer index
  408. last_sync_attempt = 0;
  409. }
  410. else
  411. {
  412. last_sync_attempt = now;
  413. }
  414. }
  415. }
  416. }
  417. //Every time through the loop check and see if we need to warn the driver of a high water mark condition
  418. //in the billing log (running out of space, and our warning frequency timer is up). If a driver has just
  419. //freshly logged in, we send a warning and reset our frequency timers.
  420. handle_watermark_warnings(&ctx, paddle_ack_flag);
  421. paddle_ack_flag = 0;
  422. nfds=0;
  423. //If we have a connection to the billing server
  424. if(server_fd >= 0)
  425. {
  426. //tell poll that we care about it
  427. fds[nfds].fd = server_fd;
  428. fds[nfds].events = POLLIN;
  429. //See if the database thinks we have anything that we need to try and sync right now...
  430. next_sync_idx = next_pending_entry(&ctx);
  431. if(next_sync_idx >= 0) //If so, ask poll() to unblock us when there is room in the pipe to the server.
  432. {
  433. fds[nfds].events |= POLLOUT;
  434. }
  435. //and advance the counter of active file descriptors
  436. nfds++;
  437. }
  438. if(commhub_fd >= 0)
  439. {
  440. fds[nfds].fd = commhub_fd;
  441. fds[nfds].events = POLLIN;
  442. nfds++;
  443. }
  444. if(nfds > 0)
  445. {
  446. poll_return = poll(fds, nfds, POLL_TIMEOUT);
  447. }
  448. else
  449. {
  450. usleep(POLL_TIMEOUT * 1000);
  451. poll_return = 0;
  452. }
  453. if(poll_return <= 0)
  454. {
  455. continue;
  456. }
  457. for(i=0; i < nfds; i++)
  458. {
  459. if( fds[i].fd == server_fd )
  460. {
  461. if(fds[i].revents & POLLIN)
  462. {
  463. read_return = recv(fds[i].fd, input_line + input_idx, sizeof(input_line) - input_idx, 0);
  464. // If the socket has closed politely (0), or had an error other than EINTR...
  465. //
  466. if( (read_return == 0) || ((read_return < 0) && (errno != EINTR)) )
  467. {
  468. close(server_fd);
  469. server_fd = -1;
  470. // EXPERIMENTAL: this might need to go when moving to production.
  471. // It's here to try and reduce the polling frequency when no connection
  472. // is present
  473. //
  474. usleep(POLL_TIMEOUT * 1000);
  475. break;
  476. }
  477. else
  478. {
  479. //this test is required otherwise an EINTR case would be treated as -1 bytes read, and bad stuff would happen...
  480. if(read_return > 0)
  481. {
  482. input_idx += read_return;
  483. do
  484. {
  485. //Advance until we either hit the end of the buffer, or we hit a line-terminator
  486. while(checked_idx < input_idx)
  487. {
  488. if( (input_line[checked_idx] == '\r') || (input_line[checked_idx] == '\n') )
  489. {
  490. break;
  491. }
  492. else
  493. {
  494. checked_idx++;
  495. }
  496. }
  497. //If we didn't hit the end of the input...
  498. if(checked_idx != input_idx)
  499. {
  500. int j,k;
  501. //Null terminate the line we got as a string...
  502. input_line[checked_idx] = '\0';
  503. //Do something useful with the string input_buffer...
  504. if( handle_bill_reply(input_line, &ctx) < 0 )
  505. {
  506. // printf("Command Failed: \"%s\"\n", input_line);
  507. }
  508. else
  509. {
  510. //If the server has ack'd a billing entry, that means that we may need up update the pass
  511. //associated with that billing entry...
  512. prepare_message(&outgoing_msg, MAILBOX_UPDATE_PASSES, "", 0);
  513. send_message(commhub_fd, &outgoing_msg);
  514. //Update our pass time status...
  515. send_bill_update(0);
  516. }
  517. //Now that we've done that, we can bump the rest of characters to the beginning of the next line...
  518. k = input_idx - (checked_idx + 1);
  519. for(j=0; j < k; j++)
  520. {
  521. input_line[j] = input_line[j + checked_idx + 1];
  522. }
  523. input_idx = j;
  524. checked_idx = 0;
  525. }
  526. //If we have hit an overflow condition such that our buffer is full and no newline has been received
  527. if(input_idx == sizeof(input_line))
  528. {
  529. close(server_fd);
  530. server_fd = -1;
  531. break;
  532. }
  533. } while(checked_idx < input_idx);
  534. }
  535. }
  536. }
  537. else if(fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) //If we've lost connection, break this loop and poll all over again
  538. {
  539. close(server_fd);
  540. server_fd = -1;
  541. break;
  542. }
  543. if(fds[i].revents & POLLOUT)
  544. {
  545. //send more logs here
  546. send_next_log(server_fd, &ctx, next_sync_idx);
  547. real_bill_status.last_sync_time = time(NULL);
  548. send_bill_update(0);
  549. }
  550. }
  551. else if( fds[i].fd == commhub_fd )
  552. {
  553. //If we've lost connection, break this loop and poll all over again
  554. if(fds[i].revents & (POLLERR | POLLHUP | POLLNVAL))
  555. {
  556. close(commhub_fd);
  557. commhub_fd = -1;
  558. break;
  559. }
  560. if(fds[i].revents & POLLIN)
  561. {
  562. read_return = get_message(commhub_fd, &incoming_msg);
  563. if( read_return < 0 )
  564. {
  565. close(commhub_fd);
  566. commhub_fd = -1;
  567. break;
  568. }
  569. process_message(&incoming_msg);
  570. }
  571. }
  572. }
  573. }
  574. printf("Detatching from Bill Database\n");
  575. detach_from_billdb(&ctx);
  576. printf("Closing connections\n");
  577. if(server_fd >= 0)
  578. {
  579. close(server_fd);
  580. server_fd = -1;
  581. }
  582. if(commhub_fd >= 0)
  583. {
  584. close(commhub_fd);
  585. server_fd = -1;
  586. }
  587. printf("Goodbye.\n");
  588. return 0;
  589. }