659 lines
17 KiB
C
659 lines
17 KiB
C
/*
|
|
* lwftpc.c
|
|
*
|
|
* Created on: Feb 20, 2024
|
|
* Author: "SeungJu Lim"
|
|
*/
|
|
|
|
#include "lwftpc.h"
|
|
|
|
/**
|
|
*=============================================
|
|
* Debugging
|
|
*=============================================
|
|
*/
|
|
|
|
/*
|
|
* @brief Debug print for LwFTP
|
|
*/
|
|
void debugPrint(const char *format, ...) {
|
|
#ifdef LWFTP_DEBUG
|
|
va_list args;
|
|
va_start(args, format);
|
|
vprintf(format, args);
|
|
va_end(args);
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
*=============================================
|
|
* Debugging
|
|
*=============================================
|
|
*/
|
|
|
|
/**
|
|
* @brief Send command to connected FTP connection
|
|
*
|
|
* @param conn Target netconn structure
|
|
* @param data
|
|
*/
|
|
err_t lwftp_send(struct netconn *conn, char *data) {
|
|
err_t err = 0;
|
|
if (strcmp(data, "\r\n") == 0)
|
|
debugPrint(">> lwftp: ----> send <CRLF>\r\n");
|
|
else
|
|
debugPrint(">> lwftp: ----> send %s", data);
|
|
err = netconn_write(conn, data, strlen(data), NETCONN_COPY);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* @brief Send PASV command to server
|
|
*/
|
|
err_t lwftp_send_pasv(lwftp_session_t *s){
|
|
s->ctrl_state = LWFTP_PASV_SENT;
|
|
return lwftp_send(s->conn, "PASV\r\n");
|
|
}
|
|
|
|
/*
|
|
* @brief Attempt login to the server
|
|
*/
|
|
err_t lwftp_login(lwftp_session_t *s) {
|
|
char cmd[256];
|
|
if (s->ctrl_state == LWFTP_CLOSED) {
|
|
return ERR_CONN;
|
|
} else if (s->ctrl_state == LWFTP_CONNECTED
|
|
|| s->ctrl_state == LWFTP_LOGGED) {
|
|
s->ctrl_state = LWFTP_USER_SENT;
|
|
snprintf(cmd, sizeof(cmd), "USER %s\r\n", s->user);
|
|
return lwftp_send(s->conn, cmd);
|
|
}
|
|
return ERR_USE;
|
|
}
|
|
|
|
/*
|
|
* @brief
|
|
*/
|
|
err_t lwftp_data_open(lwftp_session_t *s, char *response) {
|
|
err_t err = ERR_VAL;
|
|
char *ptr;
|
|
ip_addr_t addr_d;
|
|
|
|
ptr = strchr(response, '(');
|
|
if (!ptr) return ERR_BUF;
|
|
do {
|
|
unsigned int a = strtoul(ptr + 1, &ptr, 10);
|
|
unsigned int b = strtoul(ptr + 1, &ptr, 10);
|
|
unsigned int c = strtoul(ptr + 1, &ptr, 10);
|
|
unsigned int d = strtoul(ptr + 1, &ptr, 10);
|
|
IP4_ADDR(&addr_d, a, b, c, d);
|
|
} while (0);
|
|
s->data_port = strtoul(ptr + 1, &ptr, 10) << 8;
|
|
s->data_port |= strtoul(ptr + 1, &ptr, 10) & 255;
|
|
if (*ptr != ')') return ERR_BUF;
|
|
|
|
debugPrint(">> lwftp: new data port: '%d'\r\n", s->data_port);
|
|
|
|
// if data connection already exist:
|
|
if (s->data_conn != NULL) {
|
|
debugPrint(">> lwftp: existing data connection found, closing\r\n");
|
|
netconn_close(s->data_conn);
|
|
netconn_delete(s->data_conn);
|
|
s->data_state = LWFTP_CLOSED;
|
|
s->ctrl_state = LWFTP_LOGGED;
|
|
}
|
|
|
|
// create new data connection
|
|
s->data_conn = netconn_new(NETCONN_TCP);
|
|
if (s->data_conn == NULL) {
|
|
debugPrint(">> lwftp: Failed to create new netconn for data connection\r\n");
|
|
return ERR_MEM;
|
|
}
|
|
|
|
// do connect
|
|
err = netconn_connect(s->data_conn, &addr_d, s->data_port);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
*=============================================
|
|
* FTP Commands
|
|
*=============================================
|
|
*/
|
|
|
|
/*
|
|
* @brief Close FTP connection
|
|
*/
|
|
err_t lwftp_close(lwftp_session_t *s) {
|
|
err_t err;
|
|
if (s->data_conn != NULL && s->data_state != LWFTP_DATA_CLOSED) {
|
|
netconn_close(s->data_conn);
|
|
err = netconn_delete(s->data_conn);
|
|
if (err != ERR_OK) {
|
|
debugPrint(">> lwftp: cannot close data connection with code %d\r\n", err);
|
|
return err;
|
|
}
|
|
debugPrint(">> lwftp: data connection closed successfully\r\n");
|
|
s->data_state = LWFTP_CLOSED;
|
|
}
|
|
if (s->conn != NULL && s->ctrl_state != LWFTP_CLOSED) {
|
|
netconn_close(s->conn);
|
|
err = netconn_delete(s->conn);
|
|
if (err != ERR_OK) {
|
|
debugPrint(">> lwftp: cannot close control connection with code %d\r\n", err);
|
|
return err;
|
|
}
|
|
debugPrint(">> lwftp: control connection closed successfully\r\n");
|
|
s->ctrl_state = LWFTP_CLOSED;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* @brief Store data to FTP server
|
|
* caller func
|
|
*/
|
|
err_t lwftp_store(lwftp_session_t *s, char *filename, char *data) {
|
|
err_t err;
|
|
|
|
// init error state
|
|
s->result = ERR_INPROGRESS;
|
|
// set semaphore
|
|
s->xfer_semaphore = xSemaphoreCreateBinary();
|
|
|
|
// get data port and open it
|
|
if (s->ctrl_state == LWFTP_LOGGED && s->data_state == LWFTP_DATA_CLOSED) {
|
|
// set callback
|
|
s->data_callback = lwftp_store_callback;
|
|
s->filename = filename;
|
|
s->data = data;
|
|
lwftp_send_pasv(s);
|
|
|
|
// send data immediately if data port is already opened
|
|
} else if (s->ctrl_state == LWFTP_PASV_MODE && s->data_state == LWFTP_DATA_XFER_READY) {
|
|
lwftp_store_callback(s, filename, data);
|
|
|
|
// unexpected state - busy data port or something
|
|
// need to code more exceptions
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: unexpected state\r\n");
|
|
xSemaphoreGive(s->xfer_semaphore);
|
|
}
|
|
|
|
// wait semaphore - get result from callback
|
|
if (xSemaphoreTake(s->xfer_semaphore, FTP_TIMEOUT) == pdTRUE) {
|
|
err = s->result;
|
|
s->result = ERR_OK; // reset state
|
|
vSemaphoreDelete(s->xfer_semaphore); // reset semaphore
|
|
return err;
|
|
}
|
|
return ERR_TIMEOUT;
|
|
}
|
|
|
|
/*
|
|
* @brief Store command callback
|
|
* callback func
|
|
*/
|
|
void lwftp_store_callback(lwftp_session_t *s, char *filename, char *data) {
|
|
err_t err;
|
|
char cmd[256];
|
|
size_t data_len = strlen(data);
|
|
|
|
// send STOR command
|
|
snprintf(cmd, sizeof(cmd), "STOR %s\r\n", filename);
|
|
err = lwftp_send(s->conn, cmd);
|
|
|
|
if (err == ERR_OK) {
|
|
err = netconn_write(s->data_conn, data, data_len, NETCONN_COPY);
|
|
if (err == ERR_OK) {
|
|
debugPrint(">> lwftp: data sent successfully\r\n");
|
|
netconn_close(s->data_conn);
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: send data failed with code %d\r\n", err);
|
|
}
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: send command failed with code %d\r\n", err);
|
|
}
|
|
|
|
// Reset callback data from FTP server
|
|
s->data_callback = NULL;
|
|
s->filename = NULL;
|
|
s->data = NULL;
|
|
// return result to caller using FTP session structure
|
|
s->result = err;
|
|
}
|
|
|
|
/*
|
|
* @brief Retrieve data from
|
|
* caller func
|
|
*/
|
|
err_t lwftp_retrieve(lwftp_session_t *s, char *filename) {
|
|
char cmd[256];
|
|
err_t err;
|
|
|
|
// Init data portz
|
|
if (s->ctrl_state == LWFTP_LOGGED) {
|
|
s->data_callback = lwftp_retrieve_callback;
|
|
s->filename = strdup(filename);
|
|
lwftp_send(s->conn, "PASV\r\n");
|
|
|
|
// Send data
|
|
} else if (s->ctrl_state == LWFTP_PASV_SENT && s->data_state == LWFTP_DATA_XFER_READY) {
|
|
snprintf(cmd, sizeof(cmd), "RETR %s\r\n", filename);
|
|
err = lwftp_send(s->conn, cmd);
|
|
if (err == ERR_OK) {
|
|
debugPrint(">> lwftp: command sent successfully\r\n");
|
|
} else {
|
|
debugPrint(">> lwftp: send command failed with code %d\r\n", err);
|
|
}
|
|
|
|
// Init callback data
|
|
s->data_callback = NULL;
|
|
s->filename = NULL;
|
|
s->data = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* @brief Retrieve command callback
|
|
* callback func
|
|
*/
|
|
void lwftp_retrieve_callback(lwftp_session_t *s, char *filename, char *data) {
|
|
lwftp_retrieve(s, filename);
|
|
}
|
|
|
|
|
|
/*
|
|
* @brief Get a list of files in the root directory of the server.
|
|
* caller func
|
|
* @param s Session structure
|
|
* @param outStr output parameter to return result of LIST command
|
|
*/
|
|
err_t lwftp_list(lwftp_session_t *s, char **outStr) {
|
|
err_t err;
|
|
|
|
// init
|
|
s->result = ERR_INPROGRESS;
|
|
*outStr = NULL;
|
|
s->outStr = outStr;
|
|
s->xfer_semaphore = xSemaphoreCreateBinary();
|
|
|
|
// open data port
|
|
if (s->ctrl_state == LWFTP_LOGGED && s->data_state == LWFTP_DATA_CLOSED) {
|
|
s->data_callback = lwftp_list_callback;
|
|
lwftp_send_pasv(s);
|
|
|
|
// send data if data port already opened
|
|
} else if (s->ctrl_state == LWFTP_PASV_SENT
|
|
&& s->data_state == LWFTP_DATA_XFER_READY) {
|
|
lwftp_list_callback(s, NULL, NULL);
|
|
|
|
// unexpected state - busy data port or something
|
|
// need to code more exceptions
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: data port is busy\r\n");
|
|
xSemaphoreGive(s->xfer_semaphore);
|
|
}
|
|
|
|
// wait semaphore - get result from callback
|
|
if (xSemaphoreTake(s->xfer_semaphore, FTP_TIMEOUT) == pdTRUE) {
|
|
err = s->result;
|
|
|
|
if (err == ERR_OK) {
|
|
size_t length = strlen(*outStr);
|
|
if (length == 0) {
|
|
debugPrint(">> lwftp: server directory is empty.\r\n");
|
|
}
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: failed to get file list.\r\n");
|
|
}
|
|
// reset session & semaphore
|
|
resetSession(s);
|
|
vSemaphoreDelete(s->xfer_semaphore);
|
|
return err;
|
|
}
|
|
return ERR_TIMEOUT;
|
|
}
|
|
|
|
/*
|
|
* @brief List command callback
|
|
* callback func
|
|
*/
|
|
void lwftp_list_callback(lwftp_session_t *s, char *filename, char *data) {
|
|
err_t err;
|
|
|
|
// set state & send command
|
|
s->ctrl_state = LWFTP_LIST_SENT;
|
|
s->data_state = LWFTP_DATA_XFER_READY;
|
|
err = lwftp_send(s->conn, "LIST\r\n");
|
|
|
|
if (err == ERR_OK) {
|
|
debugPrint(">> lwftp: command sent successfully\r\n");
|
|
// the rest of the process will be done in the data thread.
|
|
|
|
// failed to send command to the server.
|
|
// event listener also will not catch anything.
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: send command failed with code %d\r\n", err);
|
|
s->result = err;
|
|
xSemaphoreGive(s->xfer_semaphore);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*=============================================
|
|
* Data reception & processing
|
|
*=============================================
|
|
*/
|
|
|
|
/**
|
|
* @brief Called when data is finished receiving.
|
|
* @param s Session structure
|
|
* @param data Received data
|
|
* @param size Size of received data (length)
|
|
*/
|
|
void onDataReceived(lwftp_session_t *s, void *data, size_t size) {
|
|
// debugPrint(">> lwftp: Received data:\r\n%.*s\r\n", (int) size, (char*) data);
|
|
printf(">> %d\r\n", s->data_state);
|
|
if (s->data_state == LWFTP_DATA_XFERING) {
|
|
if (s->ctrl_state == LWFTP_LIST_SENT || s->ctrl_state == LWFTP_RETR_SENT) {
|
|
char *newData = (char*) malloc(size + 1);
|
|
if (newData == NULL) {
|
|
debugPrint(">> lwftp_ERROR: memory allocation failed\r\n");
|
|
s->result = ERR_MEM;
|
|
} else {
|
|
memcpy(newData, data, size);
|
|
newData[size] = '\0';
|
|
|
|
if (*(s->outStr) != NULL) {
|
|
free(*(s->outStr));
|
|
*(s->outStr) = NULL;
|
|
}
|
|
*(s->outStr) = newData;
|
|
s->result = ERR_OK;
|
|
}
|
|
s->ctrl_state = LWFTP_LOGGED;
|
|
s->data_state = LWFTP_DATA_CLOSED;
|
|
xSemaphoreGive(s->xfer_semaphore);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*=============================================
|
|
* Threads (Event listener)
|
|
*=============================================
|
|
*/
|
|
|
|
/**
|
|
* @brief Data connection event listener
|
|
* @param arg Session structure
|
|
*/
|
|
void lwftp_data_thread(void *arg) {
|
|
lwftp_session_t *s = (lwftp_session_t*) arg;
|
|
static struct netbuf *d_buf;
|
|
static char *total_data = NULL;
|
|
static size_t total_len = 0;
|
|
|
|
while (1) {
|
|
if (s->data_conn != NULL) {
|
|
while (netconn_recv(s->data_conn, &d_buf) == ERR_OK) {
|
|
void *data;
|
|
u16_t len;
|
|
do {
|
|
netbuf_data(d_buf, &data, &len);
|
|
char *new_data = (char*) realloc(total_data, total_len + len);
|
|
if (!new_data) {
|
|
debugPrint("lwftp: memory reallocation failed\r\n");
|
|
break;
|
|
} else {
|
|
total_data = new_data;
|
|
memcpy(total_data + total_len, data, len);
|
|
total_len += len;
|
|
}
|
|
} while (netbuf_next(d_buf) >= 0);
|
|
netbuf_delete(d_buf);
|
|
}
|
|
if (total_data != NULL && total_len > 0) {
|
|
onDataReceived(s, total_data, total_len);
|
|
free(total_data);
|
|
total_data = NULL;
|
|
total_len = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Control connection event listener
|
|
* @param arg Session structure
|
|
*/
|
|
void lwftp_ctrl_thread(void *arg) {
|
|
lwftp_session_t *s = (lwftp_session_t*) arg;
|
|
static struct netbuf *buf;
|
|
uint response = 0;
|
|
char cmd[256];
|
|
err_t err;
|
|
|
|
// check that the session data is valid.
|
|
if ((s->ctrl_state != LWFTP_CLOSED) || s->data_state != LWFTP_DATA_CLOSED
|
|
|| s->conn || s->data_conn || !s->user || !s->pass) {
|
|
debugPrint(">> lwftp: invalid session data\r\n");
|
|
return;
|
|
}
|
|
|
|
// create new TCP connection
|
|
s->conn = netconn_new(NETCONN_TCP);
|
|
err = netconn_bind(s->conn, &s->cli_ip, 0);
|
|
|
|
if (err != ERR_OK) {
|
|
debugPrint(">> lwftp: client IP binding failed with code %d\r\n", err);
|
|
s->conn = NULL;
|
|
} else {
|
|
debugPrint(">> lwftp: client IP bind OK\r\n");
|
|
err = netconn_connect(s->conn, &s->svr_ip, s->svr_port);
|
|
|
|
// If the connection to the server is established, the following will continue, else delete the connection
|
|
if (err != ERR_OK) {
|
|
debugPrint(">> lwftp: server connection failed with code %d\r\n", err);
|
|
s->conn = NULL;
|
|
} else {
|
|
|
|
while (1) {
|
|
/* wait until the data is sent by the server*/
|
|
if (netconn_recv(s->conn, &buf) == ERR_OK) {
|
|
if (buf) {
|
|
response = strtoul(buf->p->payload, NULL, 10);
|
|
debugPrint("\n>> lwftp: <==== resp '%d'\r\n", response);
|
|
|
|
/** ============================================ */
|
|
/** ========= Response code processing ========= */
|
|
|
|
/** [Response 220] Service ready for new user.*/
|
|
if (response == 220) {
|
|
if (s->ctrl_state == LWFTP_CLOSED) {
|
|
debugPrint(">> lwftp: server connect OK\r\n");
|
|
s->ctrl_state = LWFTP_CONNECTED;
|
|
lwftp_login(s);
|
|
}
|
|
}
|
|
|
|
/** [Response 331] User name okay, need password.*/
|
|
else if (response == 331) {
|
|
if (s->ctrl_state == LWFTP_USER_SENT) {
|
|
s->ctrl_state = LWFTP_PASS_SENT;
|
|
snprintf(cmd, sizeof(cmd), "PASS %s\r\n", s->pass);
|
|
lwftp_send(s->conn, cmd);
|
|
}
|
|
}
|
|
|
|
/** [Response 230] User logged in, proceed.*/
|
|
else if (response == 230) {
|
|
if (s->ctrl_state == LWFTP_PASS_SENT) {
|
|
s->ctrl_state = LWFTP_LOGGED;
|
|
debugPrint(">> lwftp: now logged in\r\n");
|
|
}
|
|
}
|
|
|
|
/** [Response 227] Entering Passive Mode (h1,h2,h3,h4,p1,p2).*/
|
|
else if (response == 227) {
|
|
if (s->ctrl_state == LWFTP_PASV_SENT) {
|
|
debugPrint(">> lwftp: entering passive Mode\r\n");
|
|
err = lwftp_data_open(s, buf->p->payload);
|
|
|
|
if (err == ERR_OK) {
|
|
debugPrint(">> lwftp: data port connect OK\r\n");
|
|
s->data_state = LWFTP_DATA_XFER_READY;
|
|
// if callback exists, execute.
|
|
if (s->data_callback != NULL) {
|
|
s->data_callback(s, s->filename, s->data);
|
|
}
|
|
} else {
|
|
debugPrint(">> lwftp_ERROR: failed to connect to server data port with code %d\r\n", err);
|
|
netconn_delete(s->data_conn);
|
|
s->data_conn = NULL;
|
|
s->ctrl_state = LWFTP_LOGGED;
|
|
s->data_state = LWFTP_DATA_CLOSED;
|
|
// if callback exists, don't execute and release semaphore.
|
|
if (s->data_callback != NULL) {
|
|
xSemaphoreGive(s->xfer_semaphore);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** [Response 125] Data connection already open; transfer starting.*/
|
|
else if (response == 125) {
|
|
printf(">>c: %d / d: %d\r\n", s->ctrl_state,s->data_state);
|
|
if (s->data_state == LWFTP_DATA_XFER_READY) {
|
|
s->data_state = LWFTP_DATA_XFERING;
|
|
debugPrint(">> lwftp: data transfer starting.\r\n");
|
|
}
|
|
}
|
|
|
|
/** [Response 226] Closing data connection. Requested file action successful*/
|
|
else if (response == 226) {
|
|
// if (s->data_state == LWFTP_DATA_XFERING) {}
|
|
debugPrint(">> lwftp: transfer complete, closing connection\r\n");
|
|
}
|
|
|
|
/** [Response 332] Need account for login.*/
|
|
else if (response == 332) {
|
|
debugPrint("\n>> lwftp: need account for login.\r\n");
|
|
s->ctrl_state = LWFTP_CONNECTED;
|
|
}
|
|
|
|
/** [Response 421] Service not available, closing.*/
|
|
else if (response == 421) {
|
|
debugPrint("\n>> lwftp: service not available, closing.\r\n");
|
|
break;
|
|
}
|
|
|
|
/** [Response 500] Syntax error, command unrecognized.*/
|
|
else if (response == 500 || response == 503) {
|
|
debugPrint("\n>> lwftp: syntax error or bad sequence of commands\r\n");
|
|
if (s->ctrl_state == LWFTP_USER_SENT || s->ctrl_state == LWFTP_PASS_SENT) {
|
|
s->ctrl_state = LWFTP_CONNECTED;
|
|
} else {
|
|
s->ctrl_state = LWFTP_LOGGED;
|
|
}
|
|
}
|
|
|
|
/** [Response 530] Not logged in.*/
|
|
else if (response == 530) {
|
|
if (s->ctrl_state == LWFTP_PASS_SENT) {
|
|
s->ctrl_state = LWFTP_CONNECTED;
|
|
debugPrint(">> lwftp: invalid user data or not logged in yet\r\n");
|
|
}
|
|
}
|
|
/** ============================================ */
|
|
}
|
|
}
|
|
memset(cmd, 0, sizeof(cmd));
|
|
if (buf != NULL) {
|
|
netbuf_delete(buf);
|
|
buf = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
lwftp_close(s);
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
*=============================================
|
|
* Utils
|
|
*=============================================
|
|
*/
|
|
|
|
void resetSession(lwftp_session_t *s) {
|
|
s->data_callback = NULL;
|
|
s->filename = NULL;
|
|
s->data = NULL;
|
|
s->outStr = NULL;
|
|
s->result = ERR_OK;
|
|
}
|
|
|
|
/** Extract file names from result of FTP LIST command */
|
|
char** extractFileNames(const char *input, int *fileCount) {
|
|
int count = 0;
|
|
char **fileNames = NULL;
|
|
char *inputCopy = strdup(input);
|
|
char *line = strtok(inputCopy, "\n");
|
|
|
|
while (line != NULL) {
|
|
char *fileName = strrchr(line, ' ');
|
|
if (fileName && *(fileName + 1)) {
|
|
fileName++;
|
|
fileNames = realloc(fileNames, sizeof(char*) * (count + 1));
|
|
fileNames[count++] = strdup(fileName);
|
|
}
|
|
line = strtok(NULL, "\n");
|
|
}
|
|
free(inputCopy);
|
|
// *fileCount = count;
|
|
return fileNames;
|
|
}
|
|
|
|
/** Generate unique file name if duplicate filenames exist */
|
|
char* genUniqFilName(char **fileNames, int fileCount, const char *targetFileName) {
|
|
int suffix = 1;
|
|
char baseFileName[256] = { 0 };
|
|
char extension[256] = { 0 };
|
|
char *newFileName = NULL;
|
|
int nameExists;
|
|
|
|
const char *dot = strrchr(targetFileName, '.');
|
|
if (dot) {
|
|
memcpy(baseFileName, targetFileName, dot - targetFileName);
|
|
strcpy(extension, dot);
|
|
} else {
|
|
strcpy(baseFileName, targetFileName);
|
|
}
|
|
do {
|
|
nameExists = 0;
|
|
free(newFileName);
|
|
newFileName = (char*) malloc(strlen(baseFileName) + strlen(extension) + 15);
|
|
if (!newFileName) {
|
|
debugPrint(">> lwftp: memory allocation failed\r\n");
|
|
return NULL;
|
|
}
|
|
sprintf(newFileName, "%s(%d)%s", baseFileName, suffix, extension);
|
|
for (int i = 0; i < fileCount; i++) {
|
|
if (strcmp(fileNames[i], newFileName) == 0) {
|
|
nameExists = 1;
|
|
break;
|
|
}
|
|
}
|
|
suffix++;
|
|
} while (nameExists);
|
|
return newFileName;
|
|
}
|