* Moved the private kernel locking functions into kernelland_emu.cpp because the
actual versions cannot work in userland. * Extended the tcp_tester to be able to connect and quit connections. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19330 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -8,7 +8,6 @@ SharedLibrary libkernelland_emu.so :
|
||||
kernelland_emu.cpp
|
||||
strlcpy.c
|
||||
khash.c
|
||||
lock.c
|
||||
list.c
|
||||
|
||||
: be stdc++.r4 ;
|
||||
@@ -20,10 +19,6 @@ SEARCH on [ FGristFiles
|
||||
strlcpy.c
|
||||
] = [ FDirName $(HAIKU_TOP) src system libroot posix string ] ;
|
||||
|
||||
SEARCH on [ FGristFiles
|
||||
lock.c
|
||||
] = [ FDirName $(HAIKU_TOP) src system kernel ] ;
|
||||
|
||||
SEARCH on [ FGristFiles
|
||||
list.c khash.c
|
||||
] = [ FDirName $(HAIKU_TOP) src system kernel util ] ;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// kernelland_emu.cpp
|
||||
|
||||
#include <set>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <lock.h>
|
||||
#include <fs/devfs.h>
|
||||
|
||||
#include <Autolock.h>
|
||||
#include <Directory.h>
|
||||
@@ -15,7 +14,9 @@
|
||||
#include <Path.h>
|
||||
#include <String.h>
|
||||
|
||||
#include <fs/devfs.h>
|
||||
#include <set>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#ifdef TRACE
|
||||
#undef TRACE
|
||||
@@ -786,3 +787,234 @@ arch_int_are_interrupts_enabled(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - Private locking functions
|
||||
|
||||
|
||||
int
|
||||
recursive_lock_get_recursion(recursive_lock *lock)
|
||||
{
|
||||
thread_id thid = find_thread(NULL);
|
||||
|
||||
if (lock->holder == thid)
|
||||
return lock->recursion;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
recursive_lock_init(recursive_lock *lock, const char *name)
|
||||
{
|
||||
if (lock == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
if (name == NULL)
|
||||
name = "recursive lock";
|
||||
|
||||
lock->holder = -1;
|
||||
lock->recursion = 0;
|
||||
lock->sem = create_sem(1, name);
|
||||
|
||||
if (lock->sem >= B_OK)
|
||||
return B_OK;
|
||||
|
||||
return lock->sem;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
recursive_lock_destroy(recursive_lock *lock)
|
||||
{
|
||||
if (lock == NULL)
|
||||
return;
|
||||
|
||||
delete_sem(lock->sem);
|
||||
lock->sem = -1;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
recursive_lock_lock(recursive_lock *lock)
|
||||
{
|
||||
thread_id thid = find_thread(NULL);
|
||||
bool retval = false;
|
||||
|
||||
if (thid != lock->holder) {
|
||||
acquire_sem(lock->sem);
|
||||
|
||||
lock->holder = thid;
|
||||
retval = true;
|
||||
}
|
||||
lock->recursion++;
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
recursive_lock_unlock(recursive_lock *lock)
|
||||
{
|
||||
thread_id thid = find_thread(NULL);
|
||||
bool retval = false;
|
||||
|
||||
if (thid != lock->holder)
|
||||
panic("recursive_lock %p unlocked by non-holder thread!\n", lock);
|
||||
|
||||
if (--lock->recursion == 0) {
|
||||
lock->holder = -1;
|
||||
release_sem(lock->sem);
|
||||
retval = true;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
status_t
|
||||
mutex_init(mutex *m, const char *name)
|
||||
{
|
||||
if (m == NULL)
|
||||
return EINVAL;
|
||||
|
||||
if (name == NULL)
|
||||
name = "mutex_sem";
|
||||
|
||||
m->holder = -1;
|
||||
|
||||
m->sem = create_sem(1, name);
|
||||
if (m->sem >= B_OK)
|
||||
return B_OK;
|
||||
|
||||
return m->sem;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mutex_destroy(mutex *mutex)
|
||||
{
|
||||
if (mutex == NULL)
|
||||
return;
|
||||
|
||||
if (mutex->sem >= 0) {
|
||||
delete_sem(mutex->sem);
|
||||
mutex->sem = -1;
|
||||
}
|
||||
mutex->holder = -1;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mutex_lock(mutex *mutex)
|
||||
{
|
||||
thread_id me = find_thread(NULL);
|
||||
|
||||
// ToDo: if acquire_sem() fails, we shouldn't panic - but we should definitely
|
||||
// change the mutex API to actually return the status code
|
||||
if (acquire_sem(mutex->sem) == B_OK) {
|
||||
if (me == mutex->holder)
|
||||
panic("mutex_lock failure: mutex %p (sem = 0x%lx) acquired twice by thread 0x%lx\n", mutex, mutex->sem, me);
|
||||
}
|
||||
|
||||
mutex->holder = me;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mutex_unlock(mutex *mutex)
|
||||
{
|
||||
thread_id me = find_thread(NULL);
|
||||
|
||||
if (me != mutex->holder)
|
||||
panic("mutex_unlock failure: thread 0x%lx is trying to release mutex %p (current holder 0x%lx)\n",
|
||||
me, mutex, mutex->holder);
|
||||
|
||||
mutex->holder = -1;
|
||||
release_sem(mutex->sem);
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
status_t
|
||||
benaphore_init(benaphore *ben, const char *name)
|
||||
{
|
||||
if (ben == NULL || name == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
ben->count = 1;
|
||||
ben->sem = create_sem(0, name);
|
||||
if (ben->sem >= B_OK)
|
||||
return B_OK;
|
||||
|
||||
return ben->sem;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
benaphore_destroy(benaphore *ben)
|
||||
{
|
||||
delete_sem(ben->sem);
|
||||
ben->sem = -1;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
status_t
|
||||
rw_lock_init(rw_lock *lock, const char *name)
|
||||
{
|
||||
if (lock == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
if (name == NULL)
|
||||
name = "r/w lock";
|
||||
|
||||
lock->sem = create_sem(RW_MAX_READERS, name);
|
||||
if (lock->sem >= B_OK)
|
||||
return B_OK;
|
||||
|
||||
return lock->sem;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
rw_lock_destroy(rw_lock *lock)
|
||||
{
|
||||
if (lock == NULL)
|
||||
return;
|
||||
|
||||
delete_sem(lock->sem);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
rw_lock_read_lock(rw_lock *lock)
|
||||
{
|
||||
return acquire_sem(lock->sem);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
rw_lock_read_unlock(rw_lock *lock)
|
||||
{
|
||||
return release_sem(lock->sem);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
rw_lock_write_lock(rw_lock *lock)
|
||||
{
|
||||
return acquire_sem_etc(lock->sem, RW_MAX_READERS, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
rw_lock_write_unlock(rw_lock *lock)
|
||||
{
|
||||
return release_sem_etc(lock->sem, RW_MAX_READERS, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
|
||||
#include <KernelExport.h>
|
||||
#include <module.h>
|
||||
#include <util/AutoLock.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <new>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -36,9 +38,9 @@ extern module_info *modules[];
|
||||
extern struct net_protocol_module_info gDomainModule;
|
||||
static struct net_protocol sDomainProtocol;
|
||||
struct net_interface gInterface;
|
||||
struct net_socket_module_info gNetSocketModule;
|
||||
extern struct net_socket_module_info gNetSocketModule;
|
||||
struct net_protocol_module_info *gTCPModule;
|
||||
struct net_socket gServerSocket, gClientSocket;
|
||||
struct net_socket *gServerSocket, *gClientSocket;
|
||||
|
||||
static struct net_domain sDomain = {
|
||||
"ipv4",
|
||||
@@ -128,54 +130,68 @@ static net_stack_module_info gNetStackModule = {
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - protocol/socket
|
||||
// #pragma mark - socket
|
||||
|
||||
|
||||
net_protocol*
|
||||
init_protocol(net_socket& socket)
|
||||
status_t
|
||||
socket_create(int family, int type, int protocol, net_socket **_socket)
|
||||
{
|
||||
memset(&socket, 0, sizeof(net_socket));
|
||||
socket.family = AF_INET;
|
||||
socket.type = SOCK_STREAM;
|
||||
socket.protocol = IPPROTO_TCP;
|
||||
struct net_socket *socket = new (std::nothrow) net_socket;
|
||||
if (socket == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
memset(socket, 0, sizeof(net_socket));
|
||||
socket->family = family;
|
||||
socket->type = type;
|
||||
socket->protocol = protocol;
|
||||
|
||||
status_t status = benaphore_init(&socket->lock, "socket");
|
||||
if (status < B_OK)
|
||||
goto err1;
|
||||
|
||||
// set defaults (may be overridden by the protocols)
|
||||
socket.send.buffer_size = 65536;
|
||||
socket.send.low_water_mark = 1;
|
||||
socket.send.timeout = B_INFINITE_TIMEOUT;
|
||||
socket.receive.buffer_size = 65536;
|
||||
socket.receive.low_water_mark = 1;
|
||||
socket.receive.timeout = B_INFINITE_TIMEOUT;
|
||||
socket->send.buffer_size = 65536;
|
||||
socket->send.low_water_mark = 1;
|
||||
socket->send.timeout = B_INFINITE_TIMEOUT;
|
||||
socket->receive.buffer_size = 65536;
|
||||
socket->receive.low_water_mark = 1;
|
||||
socket->receive.timeout = B_INFINITE_TIMEOUT;
|
||||
|
||||
net_protocol* protocol = gTCPModule->init_protocol(&socket);
|
||||
if (protocol == NULL) {
|
||||
list_init_etc(&socket->pending_children, offsetof(net_socket, link));
|
||||
list_init_etc(&socket->connected_children, offsetof(net_socket, link));
|
||||
|
||||
socket->first_protocol = gTCPModule->init_protocol(socket);
|
||||
if (socket->first_protocol == NULL) {
|
||||
fprintf(stderr, "tcp_tester: cannot create protocol\n");
|
||||
return NULL;
|
||||
goto err2;
|
||||
}
|
||||
|
||||
socket.first_info = gTCPModule;
|
||||
socket.first_protocol = protocol;
|
||||
socket->first_info = gTCPModule;
|
||||
|
||||
protocol->next = &sDomainProtocol;
|
||||
protocol->module = gTCPModule;
|
||||
protocol->socket = &socket;
|
||||
socket->first_protocol->next = &sDomainProtocol;
|
||||
socket->first_protocol->module = gTCPModule;
|
||||
socket->first_protocol->socket = socket;
|
||||
|
||||
status_t status = gTCPModule->open(protocol);
|
||||
if (status < B_OK) {
|
||||
fprintf(stderr, "tcp_tester: cannot open client: %s\n", strerror(status));
|
||||
return NULL;
|
||||
}
|
||||
*_socket = socket;
|
||||
return B_OK;
|
||||
|
||||
return protocol;
|
||||
err2:
|
||||
benaphore_destroy(&socket->lock);
|
||||
err1:
|
||||
delete socket;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
close_protocol(net_protocol* protocol)
|
||||
socket_delete(net_socket *socket)
|
||||
{
|
||||
gTCPModule->close(protocol);
|
||||
gTCPModule->free(protocol);
|
||||
gTCPModule->uninit_protocol(protocol);
|
||||
if (socket->parent != NULL)
|
||||
panic("socket still has a parent!");
|
||||
|
||||
socket->first_info->uninit_protocol(socket->first_protocol);
|
||||
benaphore_destroy(&socket->lock);
|
||||
delete socket;
|
||||
}
|
||||
|
||||
|
||||
@@ -261,6 +277,187 @@ socket_listen(net_socket *socket, int backlog)
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
socket_spawn_pending(net_socket *parent, net_socket **_socket)
|
||||
{
|
||||
BenaphoreLocker locker(parent->lock);
|
||||
|
||||
// We actually accept more pending connections to compensate for those
|
||||
// that never complete, and also make sure at least a single connection
|
||||
// can always be accepted
|
||||
if (parent->child_count > 3 * parent->max_backlog / 2)
|
||||
return ENOBUFS;
|
||||
|
||||
net_socket *socket;
|
||||
status_t status = socket_create(parent->family, parent->type, parent->protocol, &socket);
|
||||
if (status < B_OK)
|
||||
return status;
|
||||
|
||||
// inherit parent's properties
|
||||
socket->send = parent->send;
|
||||
socket->receive = parent->receive;
|
||||
socket->options = parent->options & ~SO_ACCEPTCONN;
|
||||
socket->linger = parent->linger;
|
||||
memcpy(&socket->address, &parent->address, parent->address.ss_len);
|
||||
memcpy(&socket->peer, &parent->peer, parent->peer.ss_len);
|
||||
|
||||
// add to the parent's list of pending connections
|
||||
list_add_item(&parent->pending_children, socket);
|
||||
parent->child_count++;
|
||||
|
||||
*_socket = socket;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
socket_dequeue_connected(net_socket *parent, net_socket **_socket)
|
||||
{
|
||||
benaphore_lock(&parent->lock);
|
||||
|
||||
net_socket *socket = (net_socket *)list_remove_head_item(&parent->connected_children);
|
||||
if (socket != NULL) {
|
||||
socket->parent = NULL;
|
||||
parent->child_count--;
|
||||
*_socket = socket;
|
||||
}
|
||||
|
||||
benaphore_unlock(&parent->lock);
|
||||
return socket != NULL ? B_OK : B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
socket_set_max_backlog(net_socket *socket, uint32 backlog)
|
||||
{
|
||||
// we enforce an upper limit of connections waiting to be accepted
|
||||
if (backlog > 256)
|
||||
backlog = 256;
|
||||
|
||||
benaphore_lock(&socket->lock);
|
||||
|
||||
// first remove the pending connections, then the already connected ones as needed
|
||||
net_socket *child;
|
||||
while (socket->child_count > backlog
|
||||
&& (child = (net_socket *)list_remove_tail_item(&socket->pending_children)) != NULL) {
|
||||
child->parent = NULL;
|
||||
socket->child_count--;
|
||||
}
|
||||
while (socket->child_count > backlog
|
||||
&& (child = (net_socket *)list_remove_tail_item(&socket->connected_children)) != NULL) {
|
||||
child->parent = NULL;
|
||||
socket_delete(child);
|
||||
socket->child_count--;
|
||||
}
|
||||
|
||||
socket->max_backlog = backlog;
|
||||
benaphore_unlock(&socket->lock);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
The socket has been connected. It will be moved to the connected queue
|
||||
of its parent socket.
|
||||
*/
|
||||
status_t
|
||||
socket_connected(net_socket *socket)
|
||||
{
|
||||
net_socket *parent = socket->parent;
|
||||
if (parent == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
benaphore_lock(&parent->lock);
|
||||
|
||||
list_remove_item(&parent->pending_children, socket);
|
||||
list_add_item(&parent->connected_children, socket);
|
||||
|
||||
benaphore_unlock(&parent->lock);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
net_socket_module_info gNetSocketModule = {
|
||||
{
|
||||
NET_SOCKET_MODULE_NAME,
|
||||
0,
|
||||
std_ops
|
||||
},
|
||||
NULL, //socket_open,
|
||||
NULL, //socket_close,
|
||||
NULL, //socket_free,
|
||||
|
||||
NULL, //socket_readv,
|
||||
NULL, //socket_writev,
|
||||
NULL, //socket_control,
|
||||
|
||||
NULL, //socket_read_avail,
|
||||
NULL, //socket_send_avail,
|
||||
|
||||
NULL, //socket_send_data,
|
||||
NULL, //socket_receive_data,
|
||||
|
||||
// connections
|
||||
socket_spawn_pending,
|
||||
socket_delete,
|
||||
socket_dequeue_connected,
|
||||
socket_set_max_backlog,
|
||||
socket_connected,
|
||||
|
||||
// notifications
|
||||
NULL, //socket_request_notification,
|
||||
NULL, //socket_cancel_notification,
|
||||
NULL, //socket_notify,
|
||||
|
||||
// standard socket API
|
||||
NULL, //socket_accept,
|
||||
NULL, //socket_bind,
|
||||
NULL, //socket_connect,
|
||||
NULL, //socket_getpeername,
|
||||
NULL, //socket_getsockname,
|
||||
NULL, //socket_getsockopt,
|
||||
NULL, //socket_listen,
|
||||
NULL, //socket_recv,
|
||||
NULL, //socket_recvfrom,
|
||||
NULL, //socket_send,
|
||||
NULL, //socket_sendto,
|
||||
NULL, //socket_setsockopt,
|
||||
NULL, //socket_shutdown,
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - protocol
|
||||
|
||||
|
||||
net_protocol*
|
||||
init_protocol(net_socket** _socket)
|
||||
{
|
||||
net_socket *socket;
|
||||
status_t status = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, &socket);
|
||||
if (status < B_OK)
|
||||
return NULL;
|
||||
|
||||
status = socket->first_info->open(socket->first_protocol);
|
||||
if (status < B_OK) {
|
||||
fprintf(stderr, "tcp_tester: cannot open client: %s\n", strerror(status));
|
||||
socket_delete(socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*_socket = socket;
|
||||
return socket->first_protocol;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
close_protocol(net_protocol* protocol)
|
||||
{
|
||||
gTCPModule->close(protocol);
|
||||
gTCPModule->free(protocol);
|
||||
gTCPModule->uninit_protocol(protocol);
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - datalink
|
||||
|
||||
|
||||
@@ -493,7 +690,7 @@ server_thread(void *)
|
||||
net_socket* connectionSocket;
|
||||
sockaddr_in address;
|
||||
uint32 size = sizeof(struct sockaddr_in);
|
||||
status_t status = socket_accept(&gServerSocket, (struct sockaddr *)&address,
|
||||
status_t status = socket_accept(gServerSocket, (struct sockaddr *)&address,
|
||||
&size, &connectionSocket);
|
||||
if (status < B_OK) {
|
||||
fprintf(stderr, "SERVER: accepting failed: %s\n", strerror(status));
|
||||
@@ -523,7 +720,7 @@ do_connect(int argc, char** argv)
|
||||
address.sin_port = htons(port);
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
status_t status = socket_connect(&gClientSocket, (struct sockaddr *)&address,
|
||||
status_t status = socket_connect(gClientSocket, (struct sockaddr *)&address,
|
||||
sizeof(struct sockaddr));
|
||||
if (status < B_OK)
|
||||
fprintf(stderr, "tcp_tester: could not connect: %s\n", strerror(status));
|
||||
@@ -561,6 +758,7 @@ main(int argc, char** argv)
|
||||
|
||||
_add_builtin_module((module_info *)&gNetStackModule);
|
||||
_add_builtin_module((module_info *)&gNetBufferModule);
|
||||
_add_builtin_module((module_info *)&gNetSocketModule);
|
||||
_add_builtin_module((module_info *)&gNetDatalinkModule);
|
||||
_add_builtin_module(modules[0]);
|
||||
|
||||
@@ -568,6 +766,7 @@ main(int argc, char** argv)
|
||||
sockaddr_in interfaceAddress;
|
||||
interfaceAddress.sin_len = sizeof(sockaddr_in);
|
||||
interfaceAddress.sin_family = AF_INET;
|
||||
interfaceAddress.sin_addr.s_addr = htonl(0xc0a80001);
|
||||
gInterface.address = (sockaddr*)&interfaceAddress;
|
||||
|
||||
status = get_module("network/protocols/tcp/v1", (module_info **)&gTCPModule);
|
||||
@@ -577,10 +776,10 @@ main(int argc, char** argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
net_protocol* client = init_protocol(gClientSocket);
|
||||
net_protocol* client = init_protocol(&gClientSocket);
|
||||
if (client == NULL)
|
||||
return 1;
|
||||
net_protocol* server = init_protocol(gServerSocket);
|
||||
net_protocol* server = init_protocol(&gServerSocket);
|
||||
if (server == NULL)
|
||||
return 1;
|
||||
|
||||
@@ -594,12 +793,12 @@ main(int argc, char** argv)
|
||||
address.sin_port = htons(1024);
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
status = socket_bind(&gServerSocket, (struct sockaddr *)&address, sizeof(struct sockaddr));
|
||||
status = socket_bind(gServerSocket, (struct sockaddr *)&address, sizeof(struct sockaddr));
|
||||
if (status < B_OK) {
|
||||
fprintf(stderr, "tcp_tester: cannot bind server: %s\n", strerror(status));
|
||||
return 1;
|
||||
}
|
||||
status = socket_listen(&gServerSocket, 40);
|
||||
status = socket_listen(gServerSocket, 40);
|
||||
if (status < B_OK) {
|
||||
fprintf(stderr, "tcp_tester: server cannot listen: %s\n", strerror(status));
|
||||
return 1;
|
||||
@@ -639,13 +838,19 @@ main(int argc, char** argv)
|
||||
|| !strcmp(argv[0], "q"))
|
||||
break;
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (cmd_entry* command = sBuiltinCommands; command->name != NULL; command++) {
|
||||
if (!strncmp(command->name, argv[0], length)) {
|
||||
command->func(argc, argv);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
fprintf(stderr, "Unknown command \"%s\". Type \"help\" for a list of commands.\n", argv[0]);
|
||||
|
||||
free(argv);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user