// Author Name: Matt Strapp // Date: 25 April 2022 // x500: strap012 #include "server.hpp" #define MAX_CONCURRENCY_LIMIT 40000 // overkill #define POLL_TIMEOUT 1000 // Milliseconds #define S2C_SERVICE_FREQ 1.0 // Seconds #define USER_DATABASE "userDB" #define FILE_DATABASE "fileDB" #define FILES_DIR "files" int nConns; // total # of data sockets struct pollfd peers[MAX_CONCURRENCY_LIMIT + 1]; // sockets to be monitored by poll() struct CONN_STAT connStat[MAX_CONCURRENCY_LIMIT + 1]; // app-layer stats of the sockets BYTE *buf[MAX_CONCURRENCY_LIMIT + 1]; // Duplicate exists in server and client void RemoveConnection(int i) { close(peers[i].fd); if (i < nConns) { memmove(peers + i, peers + i + 1, (nConns - i) * sizeof(struct pollfd)); memmove(connStat + i, connStat + i + 1, (nConns - i) * sizeof(struct CONN_STAT)); free(buf[i]); memmove(buf + i, buf + i + 1, (nConns - i) * sizeof(BYTE *)); } nConns--; Log("[%s] %d active connections after removal", NAME, nConns); } void DoServer(int port) { int maxConcurrency = 20; // TODO: Change this to be a #define int listenFD = socket(AF_INET, SOCK_STREAM, 0); if (listenFD < 0) { Error("[%s] Cannot create listening socket.", NAME); } SetNonBlockIO(listenFD); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(struct sockaddr_in)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons((unsigned short)port); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); int optval = 1; int r = setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); if (r != 0) { Error("[%s] Cannot enable SO_REUSEADDR option.", NAME); } signal(SIGPIPE, SIG_IGN); if (bind(listenFD, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) { Error("[%s] Cannot bind to port %d.", NAME, port); } if (listen(listenFD, 16) != 0) { Error("[%s] Cannot listen to port %d.", NAME, port); } Log("[%s] Listening at 127.0.0.1:%d", NAME, port); nConns = 0; memset(peers, 0, sizeof(peers)); peers[0].fd = listenFD; peers[0].events = POLLRDNORM; memset(connStat, 0, sizeof(connStat)); int connID = 0; while (1) { // monitor the listening sock and data socks, nConn+1 in total int nReady = poll(peers, nConns + 1, POLL_TIMEOUT); if (nReady < 0) { Error("[%s] Invalid poll() return value.", NAME); } struct sockaddr_in clientAddr; socklen_t clientAddrLen = sizeof(clientAddr); struct timeb currentTime; ftime(¤tTime); // new incoming connection if ((peers[0].revents & POLLRDNORM) && (nConns < maxConcurrency)) { int fd = accept(listenFD, (struct sockaddr *)&clientAddr, &clientAddrLen); if (fd != -1) { SetNonBlockIO(fd); nConns++; Log("[%s] Accepted connection %d", NAME, nConns); peers[nConns].fd = fd; peers[nConns].events = POLLRDNORM; peers[nConns].revents = 0; memset(&connStat[nConns], 0, sizeof(struct CONN_STAT)); ftime(&connStat[nConns].lastTime); connStat[nConns].recInitHeader = false; // waiting to receive initial header connStat[nConns].expectingHeader = true; // message to be read is a header connStat[nConns].nToDo = HEADER_LEN; buf[nConns] = (BYTE *)malloc(BUF_LEN); memset(buf[nConns], 0, BUF_LEN); } if (--nReady <= 0) continue; } for (int i = 1; i <= nConns; i++) { // %02X, POLLERR %02X, POLLHUP %02X)", nConns, i, peers[i].revents, // POLLRDNORM, POLLWRNORM, POLLERR, POLLHUP); double timeDiff = (currentTime.time + currentTime.millitm / (double)1000.0f) - (connStat[i].lastTime.time + connStat[i].lastTime.millitm / (double)1000.0f); if (peers[i].revents & (POLLRDNORM | POLLERR | POLLHUP)) { // TODO: break out POLLERR and // POLLHUP? POLLHUP); // Process initial communication if (connStat[i].recInitHeader == false) { if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) < 0) { Log("[%s] Removing connection %d", NAME, i); RemoveConnection(i); continue; } if (connStat[i].nToDo == 0) { Header header; header.decode(buf[i]); if (!((header.m_command == CONNECT) | (header.m_command == SENDF) | (header.m_command == SENDF2) | (header.m_command == GETF))) { Log("[%s] WARN: Unexpected initial connection command %s", NAME, com2str(header.m_command)); } connStat[i].recInitHeader = true; connStat[i].expectingHeader = true; // flag needed for processing below if (processReception(i) != 0) { Log("[%s] Removing connection %d", NAME, i); RemoveConnection(i); continue; } } } // End initial communication else { // Standard polling if (connStat[i].direction == C2S) { if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) < 0) { Log("[%s] Removing connection %d", NAME, i); RemoveConnection(i); continue; } if (connStat[i].nToDo == 0) { if (processReception(i) != 0) { Log("[%s] Removing connection %d", NAME, i); RemoveConnection(i); continue; } } } // end C2S } // End standard receive } // if POLLRDNORM // a data socket is writable if (peers[i].revents & POLLWRNORM) { if (Send_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) < 0) { Log("[%s] Removing connection %d", NAME, i); RemoveConnection(i); continue; } } // Verify the connection is still valid if ((connStat[i].direction == S2C) && (timeDiff > S2C_SERVICE_FREQ) && (connStat[i].nToDo == 0) && (connStat[i].messageLen == 0) && (connStat[i].nBuffered == 0)) { // connStat[i].name, i); Header header; header.setFlags(S2C, SUCCESS, PRIV, SIGN); memcpy(header.m_name, NAME, MAX_USERNAME_LEN); header.m_command = PING; header.m_size = 0; header.encode(&buf[i][connStat[i].messageLen]); connStat[i].nBuffered = HEADER_LEN + header.m_size; connStat[i].messageLen = HEADER_LEN + header.m_size; connStat[i].nToDo = HEADER_LEN + header.m_size; peers[i].events |= POLLWRNORM; } NEXT_CONNECTION: asm("nop"); // if (--nReady <= 0) break; } // for } // while } // do_server // buf has all buffers // Stat has all stat structs // i is the index of the link being processed int processReception(int i) { Header header; header.decode(buf[i]); // header.displayContents(true); if (connStat[i] .expectingHeader) { // Expecting a header read sequence was completed if (header.m_size > 0) { connStat[i].expectingHeader = false; connStat[i].nToDo = header.m_size; } else if (header.m_size == 0) { // printServerCommand(i); doServerCommand(i); connStat[i].expectingHeader = true; connStat[i].nBuffered = 0; connStat[i].nToDo = HEADER_LEN; memset(buf[i], 0, BUF_LEN); } else { return -1; // Error, signal to caller to end connection } } else { // expecting a data read sequence was completed // printServerCommand(i); doServerCommand(i); if (connStat[i].direction == C2S) { // CONNECT events for S2C can get here connStat[i].expectingHeader = true; connStat[i].nBuffered = 0; connStat[i].nToDo = HEADER_LEN; memset(buf[i], 0, BUF_LEN); } } if (connStat[i].shouldClose) { Log("[%s] Line %d: Removing connection %d due to shouldClose", NAME, __LINE__, i); RemoveConnection(i); } return 0; } void doServerCommand(int i) { // Assume the buf[i] has a message at the beginning of the buffers Header header; header.decode(buf[i]); struct timeb currentTime; ftime(¤tTime); char message[5000] = {0}; char username[20] = {0}; char password[20] = {0}; Header outHeader; int numLoggedIn = 0; bool foundActiveUser = false; int s2cConnNumb = -1; char server_filename[5000] = {0}; char filename[5000] = {0}; int filenameLen, fileLen, offset, nextFileId, fileId; BYTE intbuf[5] = {0}; FILE *fp; switch (header.m_command) { case CONNECT: BigEndianToLittle(&buf[i][HEADER_LEN], connStat[i].id); connStat[i].direction = header.m_direction; connStat[i].linkType = MESSAGE_LINK; connStat[i].lastTime = currentTime; if (header.m_direction == C2S) { Log("[---- : %s] id: %d, direction: C2S, connection number: %d.", com2str(header.m_command), connStat[i].id, i); connStat[i].expectingHeader = true; connStat[i].nBuffered = 0; connStat[i].nToDo = HEADER_LEN; } else { Log("[---- : %s] id: %d, direction: S2C, connection number: %d.", com2str(header.m_command), connStat[i].id, i); connStat[i].nBuffered = 0; connStat[i].messageLen = 0; connStat[i].nToDo = 0; peers[i].events &= ~POLLRDNORM; peers[i].events &= ~POLLWRNORM; } memset(buf[i], 0, BUF_LEN); break; case REGISTER: memcpy(username, header.m_name, MAX_USERNAME_LEN); memcpy(password, &buf[i][HEADER_LEN], header.m_size); if (usernameQuery(USER_DATABASE, username) == true) { Log("[---- : %s] Registration failed. Username [%s] is already " "registered.", com2str(header.m_command), username); outHeader.setFlags(S2C, FAIL, PUB, SIGN); outHeader.m_command = REGISTER; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Cannot register username ["); strncat(message, username, MAX_USERNAME_LEN); strcat(message, "]. Username already registered.\n"); strcat(message, "\tUsernames may only contain characters a-z, A-Z, or 0-9.\n"); strcat(message, "\tUsernames may be 4 to 8 characters long."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } else { Log("[---- : %s] Successful registration of user %s", com2str(header.m_command), username); recordEntry(USER_DATABASE, username, password); outHeader.setFlags(S2C, SUCCESS, PUB, SIGN); outHeader.m_command = REGISTER; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Successfully registered "); strncat(message, username, MAX_USERNAME_LEN); strcat(message, "."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } break; case LOGIN: memcpy(username, header.m_name, MAX_USERNAME_LEN); memcpy(password, &buf[i][HEADER_LEN], header.m_size); if (loginQuery(USER_DATABASE, username, password) == true) { // Check that user is not currently logged in for (int x = 1; x <= nConns; x++) { if ((connStat[x].isLoggedIn == true) && (strcmp(username, connStat[x].name) == 0)) { Log("[%s : %s] Login failed. User is already logged in.", header.m_name, com2str(header.m_command)); foundActiveUser = true; outHeader.setFlags(S2C, FAIL, PUB, SIGN); outHeader.m_command = LOGIN; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Login failed. "); strncat(message, username, MAX_USERNAME_LEN); strcat(message, " is logged in on another device."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); break; } } if (!foundActiveUser) { for (int x = 1; x <= nConns; x++) { if (connStat[i].id == connStat[x].id) { memcpy(connStat[x].name, username, MAX_USERNAME_LEN); connStat[x].isLoggedIn = true; } } Log("[%s : %s] Successful login", connStat[i].name, com2str(header.m_command)); outHeader.setFlags(S2C, SUCCESS, PUB, SIGN); outHeader.m_command = LOGIN; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Welcome "); strncat(message, username, MAX_USERNAME_LEN); strcat(message, "."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, true, outHeader, message); } } else { Log("[---- : %s] Login failed: %s", com2str(header.m_command), username); outHeader.setFlags(S2C, FAIL, PUB, SIGN); outHeader.m_command = LOGIN; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Login failed: Invalid username or password."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } break; case LOGOUT: if (connStat[i].isLoggedIn) { for (int x = 1; x <= nConns; x++) { if (connStat[i].id == connStat[x].id) { // connStat[x].name); connStat[x].isLoggedIn = false; foundActiveUser = true; } } Log("[%s : %s] Successful logout.", connStat[i].name, com2str(header.m_command)); outHeader.setFlags(S2C, SUCCESS, PUB, SIGN); outHeader.m_command = LOGOUT; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Goodbye "); strcat(message, connStat[i].name); strcat(message, "."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } else { Log("[---- : %s] Failed: Connection %d is not logged in.", com2str(header.m_command), i); outHeader.setFlags(S2C, FAIL, PUB, SIGN); outHeader.m_command = LOGOUT; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Logout failed. You are not logged in."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } break; case SEND: case SENDA: case SEND2: case SENDA2: if (connStat[i].isLoggedIn) { outHeader.setFlags(S2C, SUCCESS, PUB, header.m_trace); outHeader.m_command = header.m_command; if (header.m_trace == SIGN) memcpy(outHeader.m_name, connStat[i].name, strlen(connStat[i].name)); else memcpy(outHeader.m_name, ANON, strlen(ANON)); memcpy(message, &buf[i][HEADER_LEN], header.m_size); outHeader.m_size = strlen(message); if ((header.m_command == SEND) || (header.m_command == SENDA)) { Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command), message); sendMessageToAllLoggedIn(outHeader, message); } else { memcpy(username, header.m_name, MAX_USERNAME_LEN); Log("[%s : %s %s] %s", connStat[i].name, com2str(header.m_command), username, message); sendMessageToName(username, outHeader, message); } } else { Log("[%s : %s] Failed: Connection %d is not logged in.", connStat[i].name, com2str(header.m_command), i); outHeader.setFlags(S2C, FAIL, PUB, SIGN); outHeader.m_command = header.m_command; memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Users must be logged in to '"); strcat(message, com2str(header.m_command)); strcat(message, "'. Login with '"); strcat(message, com2str(LOGIN)); strcat(message, " [username] [password]'."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } break; case SENDF: // Initial connection case SENDF2: // Initial connection BigEndianToLittle(&buf[i][HEADER_LEN], connStat[i].id); connStat[i].direction = header.m_direction; connStat[i].linkType = FILE_LINK; connStat[i].lastTime = currentTime; connStat[i].expectingHeader = true; connStat[i].nBuffered = 0; connStat[i].nToDo = HEADER_LEN; connStat[i].shouldClose = true; // flag to close this connection once it's processed for (int x = 1; x <= nConns; x++) { if ((connStat[x].direction == C2S) && (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].id == connStat[i].id)) { strncat(connStat[i].name, connStat[x].name, MAX_USERNAME_LEN); connStat[i].isLoggedIn = connStat[x].isLoggedIn; foundActiveUser = true; s2cConnNumb = x; break; } } if (s2cConnNumb == -1) { Log("Unexpected state. Received %s on new connection without S2C " "connection for messages.", com2str(header.m_command)); } else if (!connStat[i].isLoggedIn) { Log("[%s : %s] Failed: Connection %d is not logged in.", connStat[i].name, com2str(header.m_command), i); outHeader.setFlags(S2C, FAIL, PUB, SIGN); outHeader.m_command = SEND; // avoid file transfer logic on client side memcpy(outHeader.m_name, NAME, strlen(NAME)); strcat(message, "Users must be logged in to '"); strncat(message, com2str(header.m_command), MAX_COMMAND_LEN); strcat(message, "'. Login with '"); strncat(message, com2str(LOGIN), MAX_COMMAND_LEN); strcat(message, " [username] [password]'."); outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); } else { // Save the file nextFileId = getNumEntries(FILE_DATABASE); if (nextFileId == -1) Log("Unexpected state. %s next File Id of -1.", FILE_DATABASE); else { snprintf(server_filename, sizeof(server_filename), "%s/%d_", FILES_DIR, nextFileId); offset = HEADER_LEN + 4; BigEndianToLittle(&buf[i][offset], filenameLen); // first 4 bytes is client id offset += 4; memcpy(&server_filename[strlen(server_filename)], &buf[i][offset], filenameLen); memcpy(filename, &buf[i][offset], filenameLen); offset += filenameLen; BigEndianToLittle(&buf[i][offset], fileLen); offset += 4; fp = fopen(FILE_DATABASE, "a"); if (fp == NULL) { Log("Failed to open %s for writing.", FILE_DATABASE); } else { fputs(server_filename, fp); fputs("\n", fp); fclose(fp); } buf2file(&buf[i][offset], fileLen, server_filename); // Send SENDF/SENDF2 message to interested parties outHeader.setFlags(S2C, SUCCESS, header.m_recipient, SIGN); // will update recipient as necessary outHeader.m_command = header.m_command; memcpy(outHeader.m_name, connStat[i].name, strlen(connStat[i].name)); LittleEndianToBig(nextFileId, intbuf); memcpy(message, intbuf, 4); offset = 4; memcpy(&message[offset], &buf[i][HEADER_LEN + 8], filenameLen); offset += filenameLen; outHeader.m_size = offset; if (header.m_command == SENDF) { Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command), filename); sendMessageToAllLoggedInExceptSender(connStat[i].id, outHeader, message); } else { memcpy(username, header.m_name, MAX_USERNAME_LEN); Log("[%s : %s %s] %s", connStat[i].name, com2str(header.m_command), username, filename); sendMessageToName(username, outHeader, message); } } } break; case GETF: BigEndianToLittle(&buf[i][HEADER_LEN], connStat[i].id); // header name is origional sender. Find client's name with client id for (int x = 1; x <= nConns; x++) { if ((connStat[i].id == connStat[x].id) && (connStat[x].direction == C2S) && (connStat[x].linkType == MESSAGE_LINK)) { memcpy(connStat[i].name, connStat[x].name, MAX_USERNAME_LEN); break; } } BigEndianToLittle(&buf[i][HEADER_LEN + 4], fileId); connStat[i].direction = S2C; connStat[i].linkType = FILE_LINK; connStat[i].lastTime = currentTime; // no need for shouldClose. Client will close connection and periodic ping // will detect closure snprintf(server_filename, sizeof(server_filename), "%s/%d_", FILES_DIR, fileId); memcpy(&server_filename[strlen(server_filename)], &buf[i][HEADER_LEN + 8], header.m_size - 8); memcpy(filename, &buf[i][HEADER_LEN + 8], header.m_size - 8); filenameLen = strlen(filename); memset(buf[i], 0, BUF_LEN); // Check that the file exists fp = fopen(server_filename, "r"); if (fp) { fclose(fp); outHeader.setFlags(S2C, SUCCESS, header.m_recipient, SIGN); // will update recipient as necessary if (header.m_command == SENDF2) outHeader.m_recipient = PRIV; outHeader.m_command = header.m_command; memcpy(outHeader.m_name, header.m_name, MAX_USERNAME_LEN); // pass along origional sender offset = HEADER_LEN; LittleEndianToBig(filenameLen, &buf[i][offset]); // encode filename length offset += 4; memcpy(&buf[i][offset], filename, strlen(filename)); // encode the filename offset += strlen(filename); fileLen = file2buf(server_filename, &buf[i][offset + 4]); LittleEndianToBig(fileLen, &buf[i][offset]); offset += 4 + fileLen; outHeader.m_size = offset - HEADER_LEN; outHeader.encode(buf[i]); connStat[i].nBuffered = offset; connStat[i].messageLen = offset; connStat[i].nToDo = offset; peers[i].events &= ~POLLRDNORM; peers[i].events |= POLLWRNORM; Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command), filename); } else { Log("File not found: %s", filename); // TODO: Send message if file is not found } break; case LIST: Log("[%s : %s]", connStat[i].name, com2str(header.m_command)); outHeader.setFlags(S2C, SUCCESS, PUB, SIGN); outHeader.m_command = LIST; memcpy(outHeader.m_name, NAME, strlen(NAME)); for (int x = 1; x <= nConns; x++) { if ((connStat[x].direction == C2S) && (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].isLoggedIn)) { numLoggedIn++; } } snprintf(message, sizeof(message), "%d", numLoggedIn); strcat(message, " user(s) currently logged in:"); for (int x = 1; x <= nConns; x++) { if ((connStat[x].direction == C2S) && (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].isLoggedIn)) { strcat(message, "\n\t"); strncat(message, connStat[x].name, MAX_USERNAME_LEN); } } outHeader.m_size = strlen(message); sendMessageToId(connStat[i].id, false, outHeader, message); break; case PING: // PONG break; default: Log("Unknown command: %s", com2str(header.m_command)); } } // Send the message to every S2C with the specified id. If the reqLoggedIn // flag is set, require that the connection is logged in. void sendMessageToId(int id, bool reqLoggedIn, Header header, char *message) { for (int i = 1; i <= nConns; i++) { if ((connStat[i].direction == S2C) && (id == connStat[i].id)) { if (reqLoggedIn) { if (connStat[i].isLoggedIn) { prepareMessage(i, header, message); } } else { prepareMessage(i, header, message); } } } } // Send the message to every S2C which is logged in. void sendMessageToAllLoggedIn(Header header, char *message) { for (int i = 1; i <= nConns; i++) { if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn) { prepareMessage(i, header, message); } } } // Send the message to every s2cConnNumb void sendMessageToAllLoggedInExceptSender(int senderID, Header header, char *message) { for (int i = 1; i <= nConns; i++) { if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn && (connStat[i].id != senderID)) { prepareMessage(i, header, message); } } } // Send the message to all loged in users with the specified name. void sendMessageToName(char *name, Header header, char *message) { for (int i = 1; i <= nConns; i++) { if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn && (strcmp(name, connStat[i].name) == 0)) { prepareMessage(i, header, message); } } } // Load a message into a connection void prepareMessage(int i, Header header, char *message) { if (connStat[i].direction == S2C) { int offset = connStat[i].nBuffered; // header.displayContents(true); // connStat[i].nBuffered, connStat[i].nToDo, connStat[i].messageLen); header.encode(&buf[i][offset]); memcpy(&buf[i][offset + HEADER_LEN], message, header.m_size); connStat[i].nBuffered += HEADER_LEN + header.m_size; if (connStat[i].nToDo == 0) { connStat[i].messageLen = HEADER_LEN + header.m_size; connStat[i].nToDo = HEADER_LEN + header.m_size; } peers[i].events |= POLLWRNORM; } else { Log("[%s] WARN: attempted to write to C2S connection %d", NAME, i); } } void printServerCommand(int i) { Header h; h.decode(buf[i]); Log("[%s]: [%s, %s, %s, %s] [%s] [%s] [%d]", connStat[i].name, !h.m_direction ? "C2S" : "S2C", !h.m_flag ? "SUCCESS" : "FAIL", !h.m_recipient ? "PUB" : "PRIV", !h.m_trace ? "SIGN" : "ANNON", com2str(h.m_command), h.m_name, h.m_size); if (h.m_size > 0) Log("\tData: [%s]", buf[i] + HEADER_LEN); } int main(int argc, char **argv) { if (argc != 2) { Log("Usage: %s reset", argv[0]); Log("Usage: %s [server Port]", argv[0]); return -1; } if (strcmp("reset", argv[1]) == 0) { Log("Clearing databases"); if (remove(USER_DATABASE) != 0) Error("Error deleting %s", USER_DATABASE); if (remove(FILE_DATABASE) != 0) Error("Error deleting %s", FILE_DATABASE); exit(EXIT_SUCCESS); } // Make directory if it doesn't exist struct stat s; if (stat("files", &s) != 0) { if (mkdir("files", S_IRWXU | S_IRWXG | S_IRWXO) != 0) { perror("mkdir"); return -1; } } int port = atoi(argv[1]); DoServer(port); return 0; }