/** * Close sessions that have been idle for too long. * * If the time since a session last sent data is greater than the set value in the * service, it is disconnected. The connection timeout is disabled by default. */ void process_idle_sessions() { if (spinlock_acquire_nowait(&timeout_lock)) { if (hkheartbeat >= next_timeout_check) { /** Because the resolution of the timeout is one second, we only need to * check for it once per second. One heartbeat is 100 milliseconds. */ next_timeout_check = hkheartbeat + 10; spinlock_acquire(&session_spin); SESSION *all_session = get_all_sessions(); while (all_session) { if (all_session->service && all_session->client_dcb && all_session->client_dcb->state == DCB_STATE_POLLING && hkheartbeat - all_session->client_dcb->last_read > all_session->service->conn_idle_timeout * 10) { dcb_close(all_session->client_dcb); } all_session = all_session->next; } spinlock_release(&session_spin); } spinlock_release(&timeout_lock); } }
/** * Close a session with the router, this is the mechanism * by which a router may cleanup data structure etc. * * @param instance The router instance data * @param router_session The session being closed */ static void closeSession(ROUTER *instance, void *router_session) { ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; DCB* backend_dcb; CHK_CLIENT_RSES(router_cli_ses); /** * Lock router client session for secure read and update. */ if (rses_begin_locked_router_action(router_cli_ses)) { /* decrease server current connection counter */ atomic_add(&router_cli_ses->backend->server->stats.n_current, -1); backend_dcb = router_cli_ses->backend_dcb; router_cli_ses->backend_dcb = NULL; router_cli_ses->rses_closed = true; /** Unlock */ rses_end_locked_router_action(router_cli_ses); /** * Close the backend server connection */ if (backend_dcb != NULL) { CHK_DCB(backend_dcb); dcb_close(backend_dcb); } } }
/** * Send the menu page * * @param session The router session */ static void send_menu(WEB_SESSION *session) { DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, menu_page); dcb_close(dcb); }
/** * Send a blank page * * @param session The router session */ static void send_blank(WEB_SESSION *session) { DCB *dcb = session->session->client; send_html_header(dcb); send_static_html(dcb, blank_page); dcb_close(dcb); }
/** * Send the CSS * * @param session The router session */ static void send_css(WEB_SESSION *session) { DCB *dcb = session->session->client; send_html_header(dcb); send_static_html(dcb, css); dcb_close(dcb); }
static int telnetd_close(DCB *dcb) { TELNETD *telnetd = dcb->protocol; if (telnetd && telnetd->username) free(telnetd->username); dcb_close(dcb); return 0; }
static int test1() { DCB *dcb; int result; int eno = 0; /* Poll tests */ ss_dfprintf(stderr, "testpoll : Initialise the polling system."); init_test_env(NULL); poll_init(); ss_dfprintf(stderr, "\t..done\nAdd a DCB"); dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); if(dcb == NULL){ ss_dfprintf(stderr, "\nError on function call: dcb_alloc() returned NULL.\n"); return 1; } dcb->fd = socket(AF_UNIX, SOCK_STREAM, 0); if(dcb->fd < 0){ char errbuf[STRERROR_BUFLEN]; ss_dfprintf(stderr, "\nError on function call: socket() returned %d: %s\n",errno,strerror_r(errno,errbuf,sizeof(errbuf))); return 1; } if((eno = poll_add_dcb(dcb)) != 0){ ss_dfprintf(stderr, "\nError on function call: poll_add_dcb() returned %d.\n",eno); return 1; } if((eno = poll_remove_dcb(dcb)) != 0){ ss_dfprintf(stderr, "\nError on function call: poll_remove_dcb() returned %d.\n",eno); return 1; } if((eno = poll_add_dcb(dcb)) != 0){ ss_dfprintf(stderr, "\nError on function call: poll_add_dcb() returned %d.\n",eno); return 1; } ss_dfprintf(stderr, "\t..done\nStart wait for events."); sleep(10); poll_shutdown(); ss_dfprintf(stderr, "\t..done\nTidy up."); dcb_close(dcb); ss_dfprintf(stderr, "\t..done\n"); return 0; }
/** * Respond with an HTTP error * * @param session The router session * @param err The HTTP error code to send * @param msg The message to print */ static void respond_error(WEB_SESSION *session, int err, char *msg) { DCB *dcb = session->session->client; dcb_printf(dcb, "HTTP/1.1 %d %s\n", err, msg); dcb_printf(dcb, "Content-Type: text/html\n"); dcb_printf(dcb, "\n"); dcb_printf(dcb, "<HTML><BODY>\n"); dcb_printf(dcb, "MaxScale webserver plugin unable to satisfy request.\n"); dcb_printf(dcb, "<P>Code: %d, %s\n", err, msg); dcb_printf(dcb, "</BODY></HTML>"); dcb_close(dcb); }
/** * Send the monitors page. This iterates on all the monitors and send * the rows via the monitor_monitor. * * @param session The router session */ static void send_monitors(WEB_SESSION *session) { DCB *dcb = session->session->client; send_html_header(dcb); dcb_printf(dcb, "<HTML><HEAD>"); dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">"); dcb_printf(dcb, "<BODY><H2>Monitors</H2><P>"); dcb_printf(dcb, "<TABLE><TR><TH>Monitor</TH><TH>State</TH></TR>\n"); monitorIterate(monitor_row, dcb); dcb_printf(dcb, "</TABLE></BODY></HTML>\n"); dcb_close(dcb); }
/** * Handler for the EPOLLIN event when the DCB refers to the listening * socket for the protocol. * * @param dcb The descriptor control block */ static int httpd_accept(DCB *dcb) { int n_connect = 0; while (1) { int so = -1; struct sockaddr_in addr; socklen_t addrlen; DCB *client = NULL; HTTPD_session *client_data = NULL; if ((so = accept(dcb->fd, (struct sockaddr *)&addr, &addrlen)) == -1) { return n_connect; } else { atomic_add(&dcb->stats.n_accepts, 1); if ((client = dcb_alloc(DCB_ROLE_REQUEST_HANDLER))) { client->listen_ssl = dcb->listen_ssl; client->fd = so; client->remote = strdup(inet_ntoa(addr.sin_addr)); memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL)); /* create the session data for HTTPD */ client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session)); client->data = client_data; client->session = session_alloc(dcb->session->service, client); if (NULL == client->session || poll_add_dcb(client) == -1) { close(so); dcb_close(client); return n_connect; } n_connect++; } else { close(so); } } } return n_connect; }
/** * test1 Allocate a dcb and do lots of other things * */ static int test1() { DCB *dcb, *extra, *clone; int size = 100; int bite1 = 35; int bite2 = 60; int bite3 = 10; int buflen; /* Single buffer tests */ ss_dfprintf(stderr, "testdcb : creating buffer with type DCB_ROLE_SERVICE_LISTENER"); dcb = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); printDCB(dcb); ss_info_dassert(dcb_isvalid(dcb), "New DCB must be valid"); ss_dfprintf(stderr, "\t..done\nAllocated dcb."); clone = dcb_clone(dcb); ss_dfprintf(stderr, "\t..done\nCloned dcb"); printAllDCBs(); ss_info_dassert(true, "Something is true"); ss_dfprintf(stderr, "\t..done\n"); dcb_close(dcb); ss_dfprintf(stderr, "Freed original dcb"); ss_info_dassert(!dcb_isvalid(dcb), "Freed DCB must not be valid"); ss_dfprintf(stderr, "\t..done\nMake clone DCB a zombie"); clone->state = DCB_STATE_NOPOLLING; dcb_close(clone); ss_info_dassert(dcb_get_zombies() == clone, "Clone DCB must be start of zombie list now"); ss_dfprintf(stderr, "\t..done\nProcess the zombies list"); dcb_process_zombies(0); ss_dfprintf(stderr, "\t..done\nCheck clone no longer valid"); ss_info_dassert(!dcb_isvalid(clone), "After zombie processing, clone DCB must not be valid"); ss_dfprintf(stderr, "\t..done\n"); return 0; }
/** * Send the servers page * * @param session The router session */ static void send_servers(WEB_SESSION *session) { DCB *dcb = session->session->client; send_html_header(dcb); dcb_printf(dcb, "<HTML><HEAD>"); dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">"); dcb_printf(dcb, "<BODY><H2>Servers</H2><P>"); dcb_printf(dcb, "<TABLE><TR><TH>Server</TH><TH>Address</TH><TH>"); dcb_printf(dcb, "Port</TH><TH>State</TH><TH>Connections</TH></TR>\n"); serverIterate(server_row, dcb); dcb_printf(dcb, "</TABLE></BODY></HTML>\n"); dcb_close(dcb); }
/** * Send the services page. This produces a table by means of the * serviceIterate call. * * @param session The router session */ static void send_services(WEB_SESSION *session) { DCB *dcb = session->session->client; send_html_header(dcb); dcb_printf(dcb, "<HTML><HEAD>"); dcb_printf(dcb, "<LINK REL=\"stylesheet\" type=\"text/css\" href=\"styles.css\">"); dcb_printf(dcb, "<BODY><H2>Services</H2><P>"); dcb_printf(dcb, "<TABLE><TR><TH>Name</TH><TH>Router</TH><TH>"); dcb_printf(dcb, "Current Sessions</TH><TH>Total Sessions</TH></TR>\n"); serviceIterate(service_row, dcb); dcb_printf(dcb, "</TABLE></BODY></HTML>\n"); dcb_close(dcb); }
/** * Handler for the EPOLLERR event. * * @param dcb The descriptor control block */ static int httpd_error(DCB *dcb) { HTTPD_session *client_data = NULL; if (dcb->data) { client_data = dcb->data; if (client_data->url) { free(client_data->url); client_data->url = NULL; } free(dcb->data); dcb->data = NULL; } dcb_close(dcb); return 0; }
/** * Error Handler routine * * The routine will handle errors that occurred in backend writes. * * @param instance The router instance * @param router_session The router session * @param message The error message to reply * @param backend_dcb The backend DCB * @param action The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT * @param succp Result of action: true iff router can continue * */ static void handleError( ROUTER *instance, void *router_session, GWBUF *errbuf, DCB *backend_dcb, error_action_t action, bool *succp) { DCB *client_dcb; SESSION *session = backend_dcb->session; session_state_t sesstate; /** Don't handle same error twice on same DCB */ if (backend_dcb->dcb_errhandle_called) { /** we optimistically assume that previous call succeed */ *succp = true; return; } else { backend_dcb->dcb_errhandle_called = true; } spinlock_acquire(&session->ses_lock); sesstate = session->state; client_dcb = session->client_dcb; if (sesstate == SESSION_STATE_ROUTER_READY) { CHK_DCB(client_dcb); spinlock_release(&session->ses_lock); client_dcb->func.write(client_dcb, gwbuf_clone(errbuf)); } else { spinlock_release(&session->ses_lock); } /** false because connection is not available anymore */ dcb_close(backend_dcb); *succp = false; }
/** * We have data from the client, we must route it to the backend. * This is simply a case of sending it to the connection that was * chosen when we started the client session. * * @param instance The router instance * @param router_session The router session returned from the newSession call * @param queue The queue of data buffers to route * @return The number of bytes sent */ static int execute(ROUTER *instance, void *router_session, GWBUF *queue) { CLI_SESSION *session = (CLI_SESSION *)router_session; /* Extract the characters */ while (queue) { strncat(session->cmdbuf, GWBUF_DATA(queue), GWBUF_LENGTH(queue)); queue = gwbuf_consume(queue, GWBUF_LENGTH(queue)); } if (strrchr(session->cmdbuf, '\n')) { if (execute_cmd(session)) dcb_printf(session->session->client, "MaxScale> "); else dcb_close(session->session->client); } return 1; }
/** * Start an individual port/protocol pair * * @param service The service * @param port The port to start * @return The number of listeners started */ static int serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) { int listeners = 0; char config_bind[40]; GWPROTOCOL *funcs; port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); if (port->listener == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create listener for service %s.", service->name))); goto retblock; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; if (service->users == NULL) { /* * Allocate specific data for MySQL users * including hosts and db names */ service->users = mysql_users_alloc(); if ((loaded = load_mysql_users(service)) < 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load users from %s:%d for " "service %s.", (port->address == NULL ? "0.0.0.0" : port->address), port->port, service->name))); { /* Try loading authentication data from file cache */ char *ptr, path[4097]; strcpy(path, "/usr/local/mariadb-maxscale"); if ((ptr = getenv("MAXSCALE_HOME")) != NULL) { strncpy(path, ptr, 4096); } strncat(path, "/", 4096); strncat(path, service->name, 4096); strncat(path, "/.cache/dbusers", 4096); loaded = dbusers_load(service->users, path); if (loaded != -1) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Using cached credential information."))); } } if (loaded == -1) { hashtable_free(service->users->data); free(service->users); dcb_free(port->listener); port->listener = NULL; goto retblock; } } else { /* Save authentication data to file cache */ char *ptr, path[4097]; int mkdir_rval = 0; strcpy(path, "/usr/local/mariadb-maxscale"); if ((ptr = getenv("MAXSCALE_HOME")) != NULL) { strncpy(path, ptr, 4096); } strncat(path, "/", 4096); strncat(path, service->name, 4096); if (access(path, R_OK) == -1) { mkdir_rval = mkdir(path, 0777); } if(mkdir_rval) { skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", path, errno, strerror(errno)); mkdir_rval = 0; } strncat(path, "/.cache", 4096); if (access(path, R_OK) == -1) { mkdir_rval = mkdir(path, 0777); } if(mkdir_rval) { skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s", path, errno, strerror(errno)); mkdir_rval = 0; } strncat(path, "/dbusers", 4096); dbusers_save(service->users, path); } if (loaded == 0) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Service %s: failed to load any user " "information. Authentication will " "probably fail as a result.", service->name))); } /* At service start last update is set to USERS_REFRESH_TIME seconds earlier. * This way MaxScale could try reloading users' just after startup */ service->rate_limit.last=time(NULL) - USERS_REFRESH_TIME; service->rate_limit.nloads=1; LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, "Loaded %d MySQL Users for service [%s].", loaded, service->name))); } } else { if (service->users == NULL) { /* Generic users table */ service->users = users_alloc(); } } if ((funcs=(GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { if (service->users->data) { hashtable_free(service->users->data); } free(service->users); dcb_free(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load protocol module %s. Listener " "for service %s not started.", port->protocol, service->name))); goto retblock; } memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL)); port->listener->session = NULL; if (port->address) sprintf(config_bind, "%s:%d", port->address, port->port); else sprintf(config_bind, "0.0.0.0:%d", port->port); if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Failed to create session to service %s.", service->name))); if (service->users->data) { hashtable_free(service->users->data); } free(service->users); dcb_close(port->listener); port->listener = NULL; goto retblock; } } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to start to listen port %d for %s %s.", port->port, port->protocol, service->name))); if (service->users->data) { hashtable_free(service->users->data); } free(service->users); dcb_close(port->listener); port->listener = NULL; } retblock: return listeners; }
/** * Read event for EPOLLIN on the httpd protocol module. * * @param dcb The descriptor control block * @return */ static int httpd_read_event(DCB* dcb) { //SESSION *session = dcb->session; //ROUTER_OBJECT *router = session->service->router; //ROUTER *router_instance = session->service->router_instance; //void *rsession = session->router_session; int numchars = 1; char buf[HTTPD_REQUESTLINE_MAXLEN-1] = ""; char *query_string = NULL; char method[HTTPD_METHOD_MAXLEN-1] = ""; char url[HTTPD_SMALL_BUFFER] = ""; int cgi = 0; size_t i, j; int headers_read = 0; HTTPD_session *client_data = NULL; client_data = dcb->data; /** * get the request line * METHOD URL HTTP_VER\r\n */ numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = '\0'; strcpy(client_data->method, method); /* check allowed http methods */ if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { //httpd_unimplemented(dcb->fd); return 0; } if (strcasecmp(method, "POST") == 0) cgi = 1; i = 0; while (ISspace(buf[j]) && (j < sizeof(buf))) { j++; } while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; /** * Get the query string if availble */ if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) query_string++; if (*query_string == '?') { cgi = 1; *query_string = '\0'; query_string++; } } /** * Get the request headers */ while ((numchars > 0) && strcmp("\n", buf)) { char *value = NULL; char *end = NULL; numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); if ( (value = strchr(buf, ':'))) { *value = '\0'; value++; end = &value[strlen(value) -1]; *end = '\0'; if (strncasecmp(buf, "Hostname", 6) == 0) { strcpy(client_data->hostname, value); } if (strncasecmp(buf, "useragent", 9) == 0) { strcpy(client_data->useragent, value); } } } if (numchars) { headers_read = 1; memcpy(&client_data->headers_received, &headers_read, sizeof(int)); } /** * Now begins the server reply */ /* send all the basic headers and close with \r\n */ httpd_send_headers(dcb, 1); /** * ToDO: launch proper content handling based on the requested URI, later REST interface * */ dcb_printf(dcb, "Welcome to HTTPD Gateway (c) %s\n\n", version_str); if (strcmp(url, "/show") == 0) { if (strlen(query_string)) { if (strcmp(query_string, "dcb") == 0) dprintAllDCBs(dcb); if (strcmp(query_string, "session") == 0) dprintAllSessions(dcb); } } /* force the client connecton close */ dcb_close(dcb); return 0; }
/** * We have a registered slave that is behind the current leading edge of the * binlog. We must replay the log entries to bring this node up to speed. * * There may be a large number of records to send to the slave, the process * is triggered by the slave COM_BINLOG_DUMP message and all the events must * be sent without receiving any new event. This measn there is no trigger into * MaxScale other than this initial message. However, if we simply send all the * events we end up with an extremely long write queue on the DCB and risk * running the server out of resources. * * The slave catchup routine will send a burst of replication events per single * call. The paramter "long" control the number of events in the burst. The * short burst is intended to be used when the master receive an event and * needs to put the slave into catchup mode. This prevents the slave taking * too much tiem away from the thread that is processing the master events. * * At the end of the burst a fake EPOLLOUT event is added to the poll event * queue. This ensures that the slave callback for processing DCB write drain * will be called and future catchup requests will be handled on another thread. * * @param router The binlog router * @param slave The slave that is behind * @param large Send a long or short burst of events * @return The number of bytes written */ int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) { GWBUF *head, *record; REP_HEADER hdr; int written, rval = 1, burst; int rotating; unsigned long burst_size; uint8_t *ptr; if (large) burst = router->long_burst; else burst = router->short_burst; burst_size = router->burst_size; spinlock_acquire(&slave->catch_lock); if (slave->cstate & CS_BUSY) { spinlock_release(&slave->catch_lock); return 0; } slave->cstate |= CS_BUSY; spinlock_release(&slave->catch_lock); if (slave->file == NULL) { rotating = router->rotating; if ((slave->file = blr_open_binlog(router, slave->binlogfile)) == NULL) { if (rotating) { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; slave->cstate &= ~CS_BUSY; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); return rval; } LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_slave_catchup failed to open binlog file %s", slave->binlogfile))); slave->cstate &= ~CS_BUSY; slave->state = BLRS_ERRORED; dcb_close(slave->dcb); return 0; } } slave->stats.n_bursts++; while (burst-- && burst_size > 0 && (record = blr_read_binlog(router, slave->file, slave->binlog_pos, &hdr)) != NULL) { head = gwbuf_alloc(5); ptr = GWBUF_DATA(head); encode_value(ptr, hdr.event_size + 1, 24); ptr += 3; *ptr++ = slave->seqno++; *ptr++ = 0; // OK head = gwbuf_append(head, record); if (hdr.event_type == ROTATE_EVENT) { unsigned long beat1 = hkheartbeat; blr_close_binlog(router, slave->file); if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_close_binlog took %d beats", hkheartbeat - beat1))); blr_slave_rotate(slave, GWBUF_DATA(record)); beat1 = hkheartbeat; if ((slave->file = blr_open_binlog(router, slave->binlogfile)) == NULL) { if (rotating) { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; slave->cstate &= ~CS_BUSY; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); return rval; } LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_slave_catchup failed to open binlog file %s", slave->binlogfile))); slave->state = BLRS_ERRORED; dcb_close(slave->dcb); break; } if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "blr_open_binlog took %d beats", hkheartbeat - beat1))); } slave->stats.n_bytes += gwbuf_length(head); written = slave->dcb->func.write(slave->dcb, head); if (written && hdr.event_type != ROTATE_EVENT) { slave->binlog_pos = hdr.next_pos; } rval = written; slave->stats.n_events++; burst_size -= hdr.event_size; } if (record == NULL) slave->stats.n_failed_read++; spinlock_acquire(&slave->catch_lock); slave->cstate &= ~CS_BUSY; spinlock_release(&slave->catch_lock); if (record) { slave->stats.n_flows++; spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); } else if (slave->binlog_pos == router->binlog_position && strcmp(slave->binlogfile, router->binlog_name) == 0) { int state_change = 0; spinlock_acquire(&router->binlog_lock); spinlock_acquire(&slave->catch_lock); /* * Now check again since we hold the router->binlog_lock * and slave->catch_lock. */ if (slave->binlog_pos != router->binlog_position || strcmp(slave->binlogfile, router->binlog_name) != 0) { slave->cstate &= ~CS_UPTODATE; slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); spinlock_release(&router->binlog_lock); poll_fake_write_event(slave->dcb); } else { if ((slave->cstate & CS_UPTODATE) == 0) { slave->stats.n_upd++; slave->cstate |= CS_UPTODATE; spinlock_release(&slave->catch_lock); spinlock_release(&router->binlog_lock); state_change = 1; } } if (state_change) { slave->stats.n_caughtup++; if (slave->stats.n_caughtup == 1) { LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "%s: Slave %s is up to date %s, %u.", router->service->name, slave->dcb->remote, slave->binlogfile, slave->binlog_pos))); } else if ((slave->stats.n_caughtup % 50) == 0) { LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "%s: Slave %s is up to date %s, %u.", router->service->name, slave->dcb->remote, slave->binlogfile, slave->binlog_pos))); } } } else { if (slave->binlog_pos >= blr_file_size(slave->file) && router->rotating == 0 && strcmp(router->binlog_name, slave->binlogfile) != 0 && blr_master_connected(router)) { /* We may have reached the end of file of a non-current * binlog file. * * Note if the master is rotating there is a window during * which the rotate event has been written to the old binlog * but the new binlog file has not yet been created. Therefore * we ignore these issues during the rotate processing. */ LOGIF(LE, (skygw_log_write(LOGFILE_ERROR, "Slave reached end of file for binlong file %s at %u " "which is not the file currently being downloaded. " "Master binlog is %s, %lu.", slave->binlogfile, slave->binlog_pos, router->binlog_name, router->binlog_position))); if (blr_slave_fake_rotate(router, slave)) { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); } else { slave->state = BLRS_ERRORED; dcb_close(slave->dcb); } } else { spinlock_acquire(&slave->catch_lock); slave->cstate |= CS_EXPECTCB; spinlock_release(&slave->catch_lock); poll_fake_write_event(slave->dcb); } } return rval; }
/** * Start an individual port/protocol pair * * @param service The service * @param port The port to start * @return The number of listeners started */ static int serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) { int listeners = 0; char config_bind[40]; GWPROTOCOL *funcs; port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); if (port->listener == NULL) { return 0; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; /* Allocate specific data for MySQL users */ service->users = mysql_users_alloc(); loaded = load_mysql_users(service); /* At service start last update is set to USERS_REFRESH_TIME seconds earlier. * This way MaxScale could try reloading users' just after startup */ service->rate_limit.last=time(NULL) - USERS_REFRESH_TIME; service->rate_limit.nloads=1; LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, "Loaded %d MySQL Users.", loaded))); } else { /* Generic users table */ service->users = users_alloc(); } if ((funcs = (GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { dcb_free(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load protocol module %s. Listener " "for service %s not started.", port->protocol, service->name))); return 0; } memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL)); port->listener->session = NULL; if (port->address) sprintf(config_bind, "%s:%d", port->address, port->port); else sprintf(config_bind, "0.0.0.0:%d", port->port); if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; } else { dcb_close(port->listener); } } else { dcb_close(port->listener); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to start to listen port %d for %s %s.", port->port, port->protocol, service->name))); } return listeners; }
/** * Read event for EPOLLIN on the httpd protocol module. * * @param dcb The descriptor control block * @return */ static int httpd_read_event(DCB* dcb) { SESSION *session = dcb->session; ROUTER_OBJECT *router = session->service->router; ROUTER *router_instance = session->service->router_instance; void *rsession = session->router_session; int numchars = 1; char buf[HTTPD_REQUESTLINE_MAXLEN-1] = ""; char *query_string = NULL; char method[HTTPD_METHOD_MAXLEN-1] = ""; char url[HTTPD_SMALL_BUFFER] = ""; size_t i, j; int headers_read = 0; HTTPD_session *client_data = NULL; GWBUF *uri; client_data = dcb->data; /** * get the request line * METHOD URL HTTP_VER\r\n */ numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = '\0'; strcpy(client_data->method, method); /* check allowed http methods */ if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { //httpd_unimplemented(dcb->fd); return 0; } i = 0; while ( (j < sizeof(buf)) && ISspace(buf[j])) { j++; } while ((j < sizeof(buf) - 1) && !ISspace(buf[j]) && (i < sizeof(url) - 1)) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; /** * Get the query string if availble */ if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) { query_string++; } if (*query_string == '?') { *query_string = '\0'; query_string++; } } /** * Get the request headers */ while ((numchars > 0) && strcmp("\n", buf)) { char *value = NULL; char *end = NULL; numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); if ((value = strchr(buf, ':'))) { *value = '\0'; value++; end = &value[strlen(value) -1]; *end = '\0'; if (strncasecmp(buf, "Hostname", 6) == 0) { strcpy(client_data->hostname, value); } if (strncasecmp(buf, "useragent", 9) == 0) { strcpy(client_data->useragent, value); } } } if (numchars) { headers_read = 1; memcpy(&client_data->headers_received, &headers_read, sizeof(int)); } /** * Now begins the server reply */ /* send all the basic headers and close with \r\n */ httpd_send_headers(dcb, 1); #if 0 /** * ToDO: launch proper content handling based on the requested URI, later REST interface * */ if (strcmp(url, "/show") == 0) { if (query_string && strlen(query_string)) { if (strcmp(query_string, "dcb") == 0) { dprintAllDCBs(dcb); } if (strcmp(query_string, "session") == 0) { dprintAllSessions(dcb); } } } if (strcmp(url, "/services") == 0) { RESULTSET *set, *seviceGetList(); if ((set = serviceGetList()) != NULL) { resultset_stream_json(set, dcb); resultset_free(set); } } #endif if ((uri = gwbuf_alloc(strlen(url) + 1)) != NULL) { strcpy((char *)GWBUF_DATA(uri), url); gwbuf_set_type(uri, GWBUF_TYPE_HTTP); SESSION_ROUTE_QUERY(session, uri); } /* force the client connecton close */ dcb_close(dcb); return 0; }
/** * Associate a new session with this instance of the filter. * * Create the file to log to and open it. * * @param instance The filter instance data * @param session The session itself * @return Session specific data for this session */ static void * newSession(FILTER *instance, SESSION *session) { TEE_INSTANCE *my_instance = (TEE_INSTANCE *) instance; TEE_SESSION *my_session; char *remote, *userName; if (strcmp(my_instance->service->name, session->service->name) == 0) { MXS_ERROR("%s: Recursive use of tee filter in service.", session->service->name); my_session = NULL; goto retblock; } HASHTABLE* ht = hashtable_alloc(100, simple_str_hash, strcmp); bool is_loop = detect_loops(my_instance, ht, session->service); hashtable_free(ht); if (is_loop) { MXS_ERROR("%s: Recursive use of tee filter in service.", session->service->name); my_session = NULL; goto retblock; } if ((my_session = calloc(1, sizeof(TEE_SESSION))) != NULL) { my_session->active = 1; my_session->residual = 0; my_session->tee_replybuf = NULL; my_session->client_dcb = session->client; my_session->instance = my_instance; my_session->client_multistatement = false; my_session->queue = NULL; spinlock_init(&my_session->tee_lock); if (my_instance->source && (remote = session_get_remote(session)) != NULL) { if (strcmp(remote, my_instance->source)) { my_session->active = 0; MXS_WARNING("Tee filter is not active."); } } userName = session_getUser(session); if (my_instance->userName && userName && strcmp(userName, my_instance->userName)) { my_session->active = 0; MXS_WARNING("Tee filter is not active."); } if (my_session->active) { DCB* dcb; SESSION* ses; FILTER_DEF* dummy; UPSTREAM* dummy_upstream; if ((dcb = dcb_clone(session->client)) == NULL) { freeSession(instance, (void *) my_session); my_session = NULL; MXS_ERROR("Creating client DCB for Tee " "filter failed. Terminating session."); goto retblock; } if ((dummy = filter_alloc("tee_dummy", "tee_dummy")) == NULL) { dcb_close(dcb); freeSession(instance, (void *) my_session); my_session = NULL; MXS_ERROR("tee: Allocating memory for " "dummy filter definition failed." " Terminating session."); goto retblock; } if ((ses = session_alloc(my_instance->service, dcb)) == NULL) { filter_free(dummy); dcb_close(dcb); freeSession(instance, (void *) my_session); my_session = NULL; MXS_ERROR("Creating client session for Tee " "filter failed. Terminating session."); goto retblock; } ss_dassert(ses->ses_is_child); dummy->obj = GetModuleObject(); dummy->filter = NULL; my_session->branch_session = ses; my_session->branch_dcb = dcb; my_session->dummy_filterdef = dummy; if ((dummy_upstream = filterUpstream( dummy, my_session, &ses->tail)) == NULL) { filter_free(dummy); closeSession(instance, (void*) my_session); dcb_close(dcb); free(my_session); MXS_ERROR("tee: Allocating memory for" "dummy upstream failed." " Terminating session."); return NULL; } ses->tail = *dummy_upstream; MySQLProtocol* protocol = (MySQLProtocol*) session->client->protocol; my_session->use_ok = protocol->client_capabilities & (1 << 6); free(dummy_upstream); } } retblock: return my_session; }
static int httpd_close(DCB *dcb) { dcb_close(dcb); return 0; }
/** * Start an individual port/protocol pair * * @param service The service * @param port The port to start * @return The number of listeners started */ static int serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) { int listeners = 0; char config_bind[40]; GWPROTOCOL *funcs; port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER); if (port->listener == NULL) { return 0; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded = load_mysql_users(service); LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, "Loaded %d MySQL Users.", loaded))); } if ((funcs = (GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL) { dcb_free(port->listener); port->listener = NULL; LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to load protocol module %s. Listener " "for service %s not started.", port->protocol, service->name))); return 0; } memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL)); port->listener->session = NULL; if (port->address) sprintf(config_bind, "%s:%d", port->address, port->port); else sprintf(config_bind, "0.0.0.0:%d", port->port); if (port->listener->func.listen(port->listener, config_bind)) { port->listener->session = session_alloc(service, port->listener); if (port->listener->session != NULL) { port->listener->session->state = SESSION_STATE_LISTENER; listeners += 1; } else { dcb_close(port->listener); } } else { dcb_close(port->listener); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to start to listen port %d for %s %s.", port->port, port->protocol, service->name))); } return listeners; }