redmine

Continued work on libclsync.

Common code was been moved from control.c to socket.c
... ... @@ -28,7 +28,7 @@
static pthread_t pthread_control;
int control_procclsyncconn(socket_procconnproc_arg_t *arg, sockcmd_t *sockcmd_p) {
int control_procclsyncconn(socket_connthreaddata_t *arg, sockcmd_t *sockcmd_p) {
clsyncconn_t *clsyncconn_p = arg->clsyncconn_p;
options_t *options_p = (options_t *)arg->arg;
... ... @@ -56,8 +56,6 @@ static inline void closecontrol(options_t *options_p) {
}
int control_loop(options_t *options_p) {
pthread_t clsyncconns_threads[SOCKET_MAX_CLSYNC+1];
struct socket_procconnproc_arg clsyncconns_args[SOCKET_MAX_CLSYNC+1] = {{0}};
// Starting
... ... @@ -103,22 +101,6 @@ int control_loop(options_t *options_p) {
continue;
}
// Cleaning up after died connections
int i=clsyncconns_last+1;
while(i) {
i--;
switch(clsyncconns_args[i].state) {
case CLSTATE_DIED:
printf_ddd("Debug3: control_loop(): Forgeting clsyncconn #%u\n", i);
pthread_join(clsyncconns_threads[i], NULL);
clsyncconns_args[i].state = CLSTATE_NONE;
break;
default:
break;
}
}
// Processing the events: accepting new clsyncconn
clsyncconn_t *clsyncconn_p = socket_accept(s);
... ... @@ -133,38 +115,24 @@ int control_loop(options_t *options_p) {
continue;
}
struct socket_procconnproc_arg *connproc_arg = &clsyncconns_args[clsyncconns_num];
#ifdef PARANOID
// Processing the events: checking if previous check were been made right
printf_dd("Debug2: control_loop(): Starting new thread for new connection.\n");
socket_connthreaddata_t *threaddata_p = socket_thread_attach(clsyncconn_p);
if(connproc_arg->state != CLSTATE_NONE) {
// This's not supposed to be
printf_e("Internal-Error: control_loop(): connproc_arg->state != CLSTATE_NONE\n");
if (threaddata_p == NULL) {
printf_e("Error: control_loop(): Cannot create a thread for connection: %s (errno: %i)\n", strerror(errno), errno);
closecontrol(options_p);
continue;
}
#endif
// Processing the events: creating a thread for new connection
threaddata_p->procfunct = control_procclsyncconn;
threaddata_p->clsyncconn_p = clsyncconn_p;
threaddata_p->arg = options_p;
threaddata_p->running = &options_p->socket;
threaddata_p->authtype = options_p->flags[SOCKETAUTH];
threaddata_p->flags = 0;
printf_ddd("Debug3: control_loop(): clsyncconns_count == %u;\tclsyncconns_last == %u;\tclsyncconn_num == %u\n",
clsyncconns_count, clsyncconns_last, clsyncconns_num);
clsyncconns_last = MAX(clsyncconns_last, clsyncconns_num);
clsyncconns_count++;
connproc_arg->procfunct = control_procclsyncconn;
connproc_arg->clsyncconn_p = clsyncconn_p;
connproc_arg->arg = options_p;
connproc_arg->running = &options_p->socket;
connproc_arg->authtype = options_p->flags[SOCKETAUTH];
connproc_arg->flags = 0;
printf_dd("Debug2: control_loop(): Starting new thread for new connection.\n");
if(pthread_create(&clsyncconns_threads[clsyncconns_num], NULL, (void *(*)(void *))socket_procclsyncconn, connproc_arg)) {
printf_e("Error: control_loop(): Cannot create a thread for connection: %s (errno: %i)\n", strerror(errno), errno);
if (socket_thread_start(threaddata_p)) {
printf_e("Error: control_loop(): Cannot start a thread for connection: %s (errno: %i)\n", strerror(errno), errno);
closecontrol(options_p);
continue;
}
... ...
... ... @@ -23,201 +23,95 @@
#include "socket.h"
#include "malloc.h"
#if 0
#include "output.h"
printf_funct _printf_ddd=NULL;
printf_funct _printf_dd=NULL;
printf_funct _printf_d=NULL;
printf_funct _printf_v=NULL;
printf_funct printf_e=printf_stderr;
write_funct _write_ddd=NULL;
write_funct _write_dd=NULL;
write_funct _write_d=NULL;
write_funct _write_v=NULL;
write_funct write_e=NULL;
struct clsync {
int s;
clsyncconn_t *conn_p;
clsyncconn_procfunct_t procfunct;
};
typedef struct clsync clsync_t;
static inline int socketcheck(int sock) {
int error_code, ret;
socklen_t error_code_len = sizeof(error_code);
if((ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, &error_code, &error_code_len))) {
return errno;
}
if(error_code) {
errno = error_code;
return error_code;
}
int libclsync_procclsyncconn(socket_connthreaddata_t *arg, sockcmd_t *sockcmd_p) {
clsync_t *clsync_p = arg->arg;
clsyncconn_t *clsyncconn_p = clsync_p->conn_p;
clsyncconn_procfunct_t procfunct = clsync_p->procfunct;
return 0;
}
#ifdef PARANOID
if (procfunct == NULL) {
printf_e("Error: libclsync_procclsyncconn(): procfunct == NULL\n");
return 0;
}
#endif
int clsyncsock_send(clsync_t *clsync, sockcmd_id_t cmd_id, ...) {
va_list ap;
int ret;
va_start(ap, cmd_id);
char prebuf0[SOCKET_BUFSIZ], prebuf1[SOCKET_BUFSIZ], sendbuf[SOCKET_BUFSIZ];
ret = 0;
switch(clsync->prot) {
case 0:
switch(clsync->subprot) {
case SUBPROT0_TEXT: {
va_list ap_copy;
if(textmessage_args[cmd_id]) {
va_copy(ap_copy, ap);
vsprintf(prebuf0, textmessage_args[cmd_id], ap_copy);
} else
*prebuf0 = 0;
va_copy(ap_copy, ap);
vsprintf(prebuf1, textmessage_descr[cmd_id], ap);
size_t sendlen = sprintf(sendbuf, "%03u %s :%s\n", cmd_id, prebuf0, prebuf1);
send(clsync->sock, sendbuf, sendlen, 0);
break;
}
/* case SUBPROT0_BINARY:
break;*/
default:
printf_e("Error: socket_send(): Unknown subprotocol with id %u.\n", clsync->subprot);
ret = EINVAL;
goto l_socket_send_end;
}
break;
switch(sockcmd_p->cmd_id) {
default:
printf_e("Error: socket_send(): Unknown protocol with id %u.\n", clsync->prot);
ret = EINVAL;
goto l_socket_send_end;
if(procfunct(arg, sockcmd_p))
socket_sendinvalid(clsyncconn_p, sockcmd_p);
break;
}
l_socket_send_end:
va_end(ap);
return ret;
return 0;
}
static inline int socket_overflow_fix(char *buf, char **data_start_p, char **data_end_p) {
printf_ddd("Debug3: socket_overflow_fix(): buf==%p; data_start==%p; data_end==%p\n", buf, *data_start_p, *data_end_p);
if(buf == *data_start_p)
return 0;
static inline int _clsync_connect_setthreaddata(socket_connthreaddata_t *threaddata_p, clsync_t *clsync_p) {
threaddata_p->procfunct = libclsync_procclsyncconn;
threaddata_p->clsyncconn_p = clsync_p->conn_p;
threaddata_p->arg = clsync_p;
threaddata_p->running = NULL;
threaddata_p->authtype = SOCKAUTH_NULL;
threaddata_p->flags = 0;
size_t ptr_diff = *data_start_p - buf;
if(*data_start_p != *data_end_p) {
*data_start_p = buf;
*data_end_p = buf;
return ptr_diff;
}
size_t data_length = *data_end_p - *data_start_p;
memmove(buf, *data_start_p, data_length);
*data_start_p = buf;
*data_end_p = &buf[data_length];
return ptr_diff;
return 0;
}
static char *recv_stps[SOCKET_MAX_LIBCLSYNC];
static char *recv_ptrs[SOCKET_MAX_LIBCLSYNC];
int clsyncsock_recv(clsync_t *clsync, sockcmd_t *sockcmd) {
static char bufs[SOCKET_MAX_LIBCLSYNC][SOCKET_BUFSIZ];
char *buf, *ptr, *start, *end;
int clsync_sock;
size_t filled_length, rest_length, recv_length, filled_length_new;
clsync_t *clsync_connect_unix(const char *const socket_path, clsyncconn_procfunct_t procfunct) {
clsync_t *clsync_p = xmalloc(sizeof(*clsync_p));
memset(clsync_p, 0, sizeof(*clsync_p));
clsync_sock = clsync->sock;
buf = bufs[clsync_sock];
start = recv_stps[clsync_sock];
start = (start==NULL ? buf : start);
ptr = recv_ptrs[clsync_sock];
ptr = (ptr==NULL ? buf : ptr);
printf_ddd("Debug3: socket_recv(): buf==%p; start==%p; ptr==%p\n", buf, start, ptr);
while(1) {
filled_length = ptr-buf;
rest_length = SOCKET_BUFSIZ-filled_length-16;
if(rest_length <= 0) {
if(!socket_overflow_fix(buf, &start, &ptr)) {
printf_d("Debug: socket_recv(): Got too big message. Ignoring.\n");
ptr = buf;
}
continue;
}
if (procfunct == NULL) {
errno = EINVAL;
return NULL;
}
recv_length = recv(clsync_sock, ptr, rest_length, 0);
filled_length_new = filled_length + recv_length;
if(recv_length <= 0)
return errno;
switch(clsync->prot) {
case 0: {
// Checking if binary
uint16_t cmd_id_binary = *(uint16_t *)buf;
clsync->subprot = (cmd_id_binary == SOCKCMD_NEGOTIATION) ? SUBPROT0_BINARY : SUBPROT0_TEXT;
// Processing
switch(clsync->subprot) {
case SUBPROT0_TEXT:
if((end=strchr(ptr, '\n'))!=NULL) {
if(sscanf(start, "%03u", (unsigned int *)&sockcmd->cmd_id) != 1)
return EBADRQC;
// TODO Process message here
goto l_socket_recv_end;
}
break;
default:
return ENOPROTOOPT;
}
break;
}
default:
return ENOPROTOOPT;
clsync_p->conn_p = socket_connect_unix(socket_path);
if(clsync_p->conn_p == NULL) {
free(clsync_p);
if(errno == EUSERS) {
printf_e("Error: clsync_connect_unix(): Too many connections.\n");
return NULL;
}
// Got unknown error. Closing control socket just in case.
printf_e("Error: clsync_connect_unix(): Cannot socket_accept(): %s (errno: %i)\n", strerror(errno), errno);
return NULL;
}
l_socket_recv_end:
// ----------------------------------
// buf ptr end filled
// cut: ---------------
// start ptr
// new new
start = &end[1];
ptr = &buf[filled_length_new];
// No data buffered. Reset "start" and "ptr".
if(start == ptr) {
start = buf;
ptr = buf;
socket_connthreaddata_t *threaddata_p = socket_thread_attach(clsync_p->conn_p);
if (threaddata_p == NULL) {
socket_cleanup(clsync_p->conn_p);
free(clsync_p);
return NULL;
}
// Remembering the values
recv_stps[clsync_sock] = start;
recv_ptrs[clsync_sock] = ptr;
_clsync_connect_setthreaddata(threaddata_p, clsync_p);
printf_ddd("Debug3: socket_recv(): buf==%p; ptr==%p; end==%p, filled=%p, buf_end==%p\n", buf, ptr, end, &buf[filled_length_new], &buf[SOCKET_BUFSIZ]);
clsync_p->procfunct = procfunct;
socket_thread_start(threaddata_p);
sockcmd->cmd_num++;
return 0;
return clsync_p;
}
clsync_t *clsyncsock_connect_unix(const char const *socket_path) {
clsync_t clsync = xmalloc
return
}
#endif
... ...
... ... @@ -1471,7 +1471,6 @@ preserve_fileaccess_end:
printf_ddd("Debug3: main(): Current errno is %i.\n", ret);
// == RUNNING ==
if(ret == 0)
ret = sync_run(&options);
... ...
... ... @@ -26,18 +26,45 @@
#include "socket.h"
#if PIC
# define SOCKET_MAX SOCKET_MAX_LIBCLSYNC
# define SOCKET_PROVIDER_LIBCLSYNC
#else
# define SOCKET_PROVIDER_CLSYNC
#endif
#ifdef SOCKET_PROVIDER_LIBCLSYNC
# define SOCKET_MAX SOCKET_MAX_LIBCLSYNC
#endif
#ifdef SOCKET_PROVIDER_CLSYNC
# define SOCKET_MAX SOCKET_MAX_CLSYNC
#endif
pthread_mutex_t socket_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t socket_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
int clsyncconns_last = -1;
int clsyncconns_count = 0;
int clsyncconns_num = 0;
int clsyncconnthreads_last = -1;
int clsyncconnthreads_count = 0;
int clsyncconnthreads_num = 0;
char clsyncconn_busy[SOCKET_MAX+1] = {0};
char clsyncconnthread_busy[SOCKET_MAX+1] = {0};
socket_connthreaddata_t connthreaddata[SOCKET_MAX+1] = {{0}};
int socket_gc() {
int i=clsyncconnthreads_last+1;
while(i) {
i--;
switch(connthreaddata[i].state) {
case CLSTATE_DIED:
printf_ddd("Debug3: socket_gc(): Forgeting clsyncconn #%u\n", i);
pthread_join(connthreaddata[i].thread, NULL);
connthreaddata[i].state = CLSTATE_NONE;
break;
default:
break;
}
}
return 0;
}
static char *recv_stps[SOCKET_MAX];
static char *recv_ptrs[SOCKET_MAX];
... ... @@ -89,60 +116,62 @@ static inline int socket_check(clsyncconn_t *clsyncconn_p) {
}
clsyncconn_t *socket_new(int clsyncconn_sock) {
pthread_mutex_lock(&socket_mutex);
if(clsyncconns_num >= SOCKET_MAX) {
printf_e("Warning: socket_new(): Too many connection to control socket. Closing the new one.\n");
close(clsyncconn_sock);
errno = EUSERS;
pthread_mutex_unlock(&socket_mutex);
return NULL;
}
clsyncconn_t *clsyncconn_p = xmalloc(sizeof(*clsyncconn_p));
printf_dd("Debug2: socket_new(): sock == %i; num == %i.\n", clsyncconn_sock, clsyncconns_num);
printf_dd("Debug2: socket_new(): sock == %i; num == %i.\n", clsyncconn_sock, clsyncconnthreads_num);
clsyncconn_p->sock = clsyncconn_sock;
clsyncconn_p->prot = SOCKET_DEFAULT_PROT;
clsyncconn_p->subprot = SOCKET_DEFAULT_SUBPROT;
clsyncconn_busy[clsyncconns_num]=1;
// TODO: SECURITY: Possible DoS-attack on huge "SOCKET_MAX" value. Fix it.
while(clsyncconn_busy[++clsyncconns_num]);
pthread_mutex_unlock(&socket_mutex);
return clsyncconn_p;
}
int socket_delete(clsyncconn_t *clsyncconn_p) {
pthread_mutex_lock(&socket_mutex);
int socket_cleanup(clsyncconn_t *clsyncconn_p) {
int clsyncconn_sock = clsyncconn_p->sock;
printf_dd("Debug2: socket_delete(): sock == %i; num == %i.\n", clsyncconn_sock, clsyncconn_p->num);
printf_dd("Debug2: socket_cleanup(): sock == %i; num == %i.\n", clsyncconn_sock, clsyncconn_p->num);
recv_ptrs[clsyncconn_sock] = NULL;
recv_stps[clsyncconn_sock] = NULL;
close(clsyncconn_sock);
clsyncconns_count--;
free(clsyncconn_p);
return 0;
}
if(clsyncconns_last == clsyncconn_p->num)
clsyncconns_last = clsyncconn_p->num-1;
int socket_thread_delete(socket_connthreaddata_t *threaddata_p) {
int thread_id;
clsyncconns_num = MIN(clsyncconn_p->num, clsyncconn_sock);
pthread_mutex_lock(&socket_thread_mutex);
clsyncconn_busy[clsyncconn_p->num]=0;
thread_id = threaddata_p->id;
free(clsyncconn_p);
pthread_mutex_unlock(&socket_mutex);
socket_cleanup(threaddata_p->clsyncconn_p);
clsyncconnthreads_count--;
if(clsyncconnthreads_last == thread_id)
clsyncconnthreads_last = thread_id-1;
clsyncconnthread_busy[thread_id]=0;
threaddata_p->state = CLSTATE_DIED;
if (threaddata_p->freefunct_arg != NULL)
threaddata_p->freefunct_arg(threaddata_p->arg);
pthread_mutex_unlock(&socket_thread_mutex);
return 0;
}
clsyncconn_t *socket_accept(int sock) {
// Cleaning up after died connections (getting free space for new connection)
socket_gc();
// Getting new connection
int clsyncconn_sock = accept(sock, NULL, NULL);
if(clsyncconn_sock == -1) {
printf_e("Error: socket_accept(%i): Cannot accept(): %s (errno: %i)\n", sock, strerror(errno), errno);
... ... @@ -152,14 +181,17 @@ clsyncconn_t *socket_accept(int sock) {
return socket_new(clsyncconn_sock);
}
#ifdef SOCKET_PROVIDER_LIBCLSYNC
clsyncconn_t *socket_connect_unix(const char *const socket_path) {
return NULL;
}
#endif
int socket_send(clsyncconn_t *clsyncconn, sockcmd_id_t cmd_id, ...) {
va_list ap;
int ret;
va_start(ap, cmd_id);
/* static char bufs[SOCKET_MAX][SOCKET_BUFSIZ], prebufs0[SOCKET_MAX][SOCKET_BUFSIZ], prebufs1[SOCKET_MAX][SOCKET_BUFSIZ];
char *prebuf0 = prebufs0[clsyncconn_sock], *prebuf1 = prebufs1[clsyncconn_sock], *sendbuf = bufs[clsyncconn_sock];
*/
char prebuf0[SOCKET_BUFSIZ], prebuf1[SOCKET_BUFSIZ], sendbuf[SOCKET_BUFSIZ];
ret = 0;
... ... @@ -394,15 +426,14 @@ l_socket_recv_end:
return 0;
}
static inline int socket_sendinvalid(clsyncconn_t *clsyncconn_p, sockcmd_t *sockcmd_p) {
int socket_sendinvalid(clsyncconn_t *clsyncconn_p, sockcmd_t *sockcmd_p) {
if(sockcmd_p->cmd_id >= 1000)
return socket_send(clsyncconn_p, SOCKCMD_REPLY_INVALIDCMDID, sockcmd_p->cmd_num);
else
return socket_send(clsyncconn_p, SOCKCMD_REPLY_UNKNOWNCMD, sockcmd_p->cmd_id, sockcmd_p->cmd_num);
}
int socket_procclsyncconn(socket_procconnproc_arg_t *arg) {
// clsyncconn_t clsyncconn = {0};
int socket_procclsyncconn(socket_connthreaddata_t *arg) {
char _sockcmd_buf[SOCKET_BUFSIZ]={0};
sockcmd_t *sockcmd_p = (sockcmd_t *)_sockcmd_buf;
... ... @@ -481,8 +512,10 @@ l_socket_procclsyncconn_sw_default:
break;
}
if(sockcmd_p->data != NULL)
if(sockcmd_p->data != NULL) {
free(sockcmd_p->data);
sockcmd_p->data = NULL;
}
// Check if the socket is still alive
if(socket_check(clsyncconn_p)) {
... ... @@ -503,9 +536,68 @@ l_socket_procclsyncconn_sw_default:
printf_ddd("Debug3: socket_procclsyncconn(): Ending a connection thread.\n");
socket_delete(clsyncconn_p);
socket_thread_delete(arg);
return 0;
}
socket_connthreaddata_t *socket_thread_new() {
pthread_mutex_lock(&socket_thread_mutex);
socket_connthreaddata_t *threaddata_p = &connthreaddata[clsyncconnthreads_num];
if(clsyncconnthreads_num >= SOCKET_MAX) {
printf_e("Warning: socket_thread_new(): Too many connection threads.\n");
errno = EUSERS;
pthread_mutex_unlock(&socket_thread_mutex);
return NULL;
}
threaddata_p->id = clsyncconnthreads_num;
clsyncconnthread_busy[clsyncconnthreads_num]=1;
// TODO: SECURITY: Possible DoS-attack on huge "SOCKET_MAX" value. Fix it.
while(clsyncconnthread_busy[++clsyncconnthreads_num]);
#ifdef PARANOID
// Processing the events: checking if previous check were been made right
if(threaddata_p->state != CLSTATE_NONE) {
// This's not supposed to be
printf_e("Internal-Error: socket_newconnarg(): connproc_arg->state != CLSTATE_NONE\n");
pthread_mutex_unlock(&socket_thread_mutex);
return NULL;
}
#endif
// Processing the events: creating a thread for new connection
printf_ddd("Debug3: socket_newconnarg(): clsyncconnthreads_count == %u;\tclsyncconnthreads_last == %u;\tclsyncconn_num == %u\n",
clsyncconnthreads_count, clsyncconnthreads_last, clsyncconnthreads_num);
clsyncconnthreads_last = MAX(clsyncconnthreads_last, clsyncconnthreads_num);
clsyncconnthreads_count++;
pthread_mutex_unlock(&socket_thread_mutex);
return threaddata_p;
}
socket_connthreaddata_t *socket_thread_attach(clsyncconn_t *clsyncconn_p) {
arg->state = CLSTATE_DIED;
socket_connthreaddata_t *threaddata_p = socket_thread_new();
if (threaddata_p == NULL)
return NULL;
threaddata_p->clsyncconn_p = clsyncconn_p;
return threaddata_p;
}
int socket_thread_start(socket_connthreaddata_t *threaddata_p) {
if(pthread_create(&threaddata_p->thread, NULL, (void *(*)(void *))socket_procclsyncconn, threaddata_p)) {
printf_e("Error: socket_thread_start(): Cannot create a thread for connection: %s (errno: %i)\n", strerror(errno), errno);
return errno;
}
return 0;
}
... ...
... ... @@ -31,6 +31,13 @@ struct clsyncconn {
};
typedef struct clsyncconn clsyncconn_t;
struct clsyncthread {
clsyncconn_t *clsyncconn_p;
void *arg;
void *funct_arg_free;
};
typedef struct clsyncthread clsyncthread_t;
enum subprot0 {
SUBPROT0_TEXT,
SUBPROT0_BINARY,
... ... @@ -126,27 +133,36 @@ enum sockauth_id {
};
typedef enum sockauth_id sockauth_id_t;
struct socket_procconnproc_arg;
typedef int (*clsyncconn_procfunct_t)(struct socket_procconnproc_arg *, sockcmd_t *);
struct socket_procconnproc_arg {
struct socket_connthreaddata;
typedef int (*clsyncconn_procfunct_t)(struct socket_connthreaddata *, sockcmd_t *);
typedef int (*freefunct_t)(void *);
struct socket_connthreaddata {
int id;
clsyncconn_procfunct_t procfunct;
freefunct_t freefunct_arg;
clsyncconn_t *clsyncconn_p;
void *arg;
clsyncconn_state_t state;
sockauth_id_t authtype;
int *running; // Pointer to interger with non-zero value to continue running
sockprocflags_t flags;
pthread_t thread;
};
typedef struct socket_procconnproc_arg socket_procconnproc_arg_t;
typedef struct socket_connthreaddata socket_connthreaddata_t;
extern int socket_send(clsyncconn_t *clsyncconn, sockcmd_id_t cmd_id, ...);
extern int socket_sendinvalid(clsyncconn_t *clsyncconn_p, sockcmd_t *sockcmd_p);
extern int socket_recv(clsyncconn_t *clsyncconn, sockcmd_t *sockcmd);
extern int socket_check_bysock(int sock);
extern int socket_close(clsyncconn_t *clsyncconn);
extern clsyncconn_t *socket_accept(int sock);
extern int socket_cleanup(clsyncconn_t *clsyncconn_p);
extern int socket_init();
extern int socket_deinit();
extern int socket_procclsyncconn(socket_procconnproc_arg_t *arg);
extern int socket_procclsyncconn(socket_connthreaddata_t *arg);
extern clsyncconn_t *socket_connect_unix(const char *const socket_path);
extern socket_connthreaddata_t *socket_thread_attach(clsyncconn_t *clsyncconn_p);
extern int socket_thread_start(socket_connthreaddata_t *threaddata_p);
extern int clsyncconns_num;
extern int clsyncconns_count;
... ...