Checked in our version of libsocket.so. When building for BONE use this command:
BONE_VERSION=1 jam This will remove the select() function from libsocket.so. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@7202 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
SubDir OBOS_TOP src kits network libsocket ;
|
||||||
|
|
||||||
|
UsePrivateHeaders net ;
|
||||||
|
|
||||||
|
{
|
||||||
|
local defines ;
|
||||||
|
if $(BONE_VERSION) {
|
||||||
|
defines += BONE_VERSION ;
|
||||||
|
}
|
||||||
|
defines = [ FDefines $(defines) ] ;
|
||||||
|
SubDirCcFlags $(defines) ;
|
||||||
|
SubDirC++Flags $(defines) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedLibrary socket :
|
||||||
|
socket.c
|
||||||
|
select.c
|
||||||
|
;
|
||||||
|
|
||||||
|
# Installation -- in the test directory for the time being
|
||||||
|
OBOSInstallRelSymLink install-networking
|
||||||
|
: [ FDirName $(OBOS_TEST_DIR) kits net lib ]
|
||||||
|
: libsocket.so
|
||||||
|
: installed-networking-test ;
|
||||||
|
|
||||||
|
Package openbeos-networkingkit-cvs :
|
||||||
|
libsocket.so :
|
||||||
|
# boot home config lib ;
|
||||||
|
boot home Desktop openbeos-networkingkit lib ;
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <image.h>
|
||||||
|
#include <OS.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "net_stack_driver.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef BONE_VERSION
|
||||||
|
|
||||||
|
static int fd_set_count(fd_set * bits, int nbits)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
for (fd = 0; fd < nbits; fd++)
|
||||||
|
if (FD_ISSET(fd, bits))
|
||||||
|
count++;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int select(int nbits, struct fd_set * rbits,
|
||||||
|
struct fd_set * wbits,
|
||||||
|
struct fd_set * ebits,
|
||||||
|
struct timeval * tv)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int n;
|
||||||
|
struct stack_driver_args args;
|
||||||
|
status_t status;
|
||||||
|
area_id area;
|
||||||
|
struct r5_selectsync * rss;
|
||||||
|
|
||||||
|
#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))
|
||||||
|
|
||||||
|
// We share a area with the fd_set on it with the network stack code.
|
||||||
|
// The r5_notify_select_event in the net_stack_driver code will fill it and wakeup us
|
||||||
|
// when a event raise.
|
||||||
|
|
||||||
|
area = create_area("r5_selectsync", (void **) &rss,
|
||||||
|
B_ANY_ADDRESS, ROUND_TO_PAGE_SIZE(sizeof(*rss)), B_NO_LOCK,
|
||||||
|
B_READ_AREA | B_WRITE_AREA);
|
||||||
|
if (area < B_OK) {
|
||||||
|
errno = area;
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The area is multi-thread protected by a semaphore locker
|
||||||
|
rss->lock = create_sem(1, "r5_selectsync_lock");
|
||||||
|
if (rss->lock < B_OK) {
|
||||||
|
errno = rss->lock;
|
||||||
|
rss->wakeup = 0;
|
||||||
|
n = -1;
|
||||||
|
goto error;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the wakeup call semaphore
|
||||||
|
rss->wakeup = create_sem(0, "r5_selectsync_wakeup");
|
||||||
|
if (rss->wakeup < B_OK) {
|
||||||
|
errno = rss->wakeup;
|
||||||
|
n = -1;
|
||||||
|
goto error;
|
||||||
|
};
|
||||||
|
|
||||||
|
FD_ZERO(&rss->rbits);
|
||||||
|
FD_ZERO(&rss->wbits);
|
||||||
|
FD_ZERO(&rss->ebits);
|
||||||
|
|
||||||
|
/* Beware, ugly hacky hack: we pass the area_id of our shared r5_selectsync,
|
||||||
|
that net_stack_driver should udate directly
|
||||||
|
*/
|
||||||
|
args.u.select.sync = (struct selectsync *) area;
|
||||||
|
|
||||||
|
/* call, indirectly, net_stack_select() for each event to monitor
|
||||||
|
* as we are not the vfs, we can't call this device hook ourself, but
|
||||||
|
* our NET_STACK_SELECT opcode would do it for us...
|
||||||
|
*/
|
||||||
|
n = 0;
|
||||||
|
for (fd = 0; fd < nbits; fd++) {
|
||||||
|
if (rbits && FD_ISSET(fd, rbits)) {
|
||||||
|
args.u.select.ref = (fd << 8) | 1;
|
||||||
|
if (ioctl(fd, NET_STACK_SELECT, &args, sizeof(args)) >= 0)
|
||||||
|
n++;
|
||||||
|
};
|
||||||
|
if (wbits && FD_ISSET(fd, wbits)) {
|
||||||
|
args.u.select.ref = (fd << 8) | 2;
|
||||||
|
if (ioctl(fd, NET_STACK_SELECT, &args, sizeof(args)) >= 0)
|
||||||
|
n++;
|
||||||
|
};
|
||||||
|
if (ebits && FD_ISSET(fd, ebits)) {
|
||||||
|
args.u.select.ref = (fd << 8) | 3;
|
||||||
|
if (ioctl(fd, NET_STACK_SELECT, &args, sizeof(args)) >= 0)
|
||||||
|
n++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < 1) {
|
||||||
|
// TODO: maybe we could hack a workaround for select() on file/dev here:
|
||||||
|
// If no fd handle the NET_STACK_SELECT, assume they're files, so always
|
||||||
|
// readable and writable?
|
||||||
|
errno = B_BAD_VALUE;
|
||||||
|
n = -1;
|
||||||
|
goto error;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Okay, now wait for our wakeup call.
|
||||||
|
// Maybe his's already there, even... (when the select event is already raised)
|
||||||
|
if (tv) {
|
||||||
|
bigtime_t timeout;
|
||||||
|
|
||||||
|
timeout = tv->tv_sec * 1000000LL + tv->tv_usec;
|
||||||
|
status = acquire_sem_etc(rss->wakeup, 1, B_RELATIVE_TIMEOUT | B_CAN_INTERRUPT, timeout);
|
||||||
|
} else
|
||||||
|
status = acquire_sem(rss->wakeup);
|
||||||
|
|
||||||
|
// we don't need it anymore, and we say "select() party is over" that way, too :-)
|
||||||
|
delete_sem(rss->lock);
|
||||||
|
delete_sem(rss->wakeup);
|
||||||
|
rss->lock = rss->wakeup = -1;
|
||||||
|
|
||||||
|
// unregister socket event notification
|
||||||
|
for(fd = 0; fd < nbits; fd++) {
|
||||||
|
if (rbits && FD_ISSET(fd, rbits)) {
|
||||||
|
args.u.select.ref = (fd << 8) | 1;
|
||||||
|
ioctl(fd, NET_STACK_DESELECT, &args, sizeof(args));
|
||||||
|
};
|
||||||
|
if (wbits && FD_ISSET(fd, wbits)) {
|
||||||
|
args.u.select.ref = (fd << 8) | 2;
|
||||||
|
ioctl(fd, NET_STACK_DESELECT, &args, sizeof(args));
|
||||||
|
};
|
||||||
|
if (ebits && FD_ISSET(fd, ebits)) {
|
||||||
|
args.u.select.ref = (fd << 8) | 3;
|
||||||
|
ioctl(fd, NET_STACK_DESELECT, &args, sizeof(args));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Okay, what the final result?
|
||||||
|
switch (status) {
|
||||||
|
case B_OK:
|
||||||
|
case B_WOULD_BLOCK:
|
||||||
|
case B_TIMED_OUT: // logicly, 'n' should stay to 0 in this case...
|
||||||
|
n = 0;
|
||||||
|
if (rbits) {
|
||||||
|
memcpy(rbits, &rss->rbits, sizeof(*rbits));
|
||||||
|
n += fd_set_count(rbits, nbits);
|
||||||
|
};
|
||||||
|
if (wbits) {
|
||||||
|
memcpy(wbits, &rss->wbits, sizeof(*wbits));
|
||||||
|
n += fd_set_count(wbits, nbits);
|
||||||
|
};
|
||||||
|
if (ebits) {
|
||||||
|
memcpy(ebits, &rss->ebits, sizeof(*ebits));
|
||||||
|
n += fd_set_count(ebits, nbits);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errno = status;
|
||||||
|
n = -1;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (rss->lock >= 0)
|
||||||
|
delete_sem(rss->lock);
|
||||||
|
if (rss->wakeup >= 0)
|
||||||
|
delete_sem(rss->wakeup);
|
||||||
|
|
||||||
|
delete_area(area);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
/* socket.c */
|
||||||
|
|
||||||
|
/* This is a very simple little shared library that acts as a wrapper
|
||||||
|
* for our device/kernel stack!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <kernel/OS.h>
|
||||||
|
#include <iovec.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/sockio.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include "net_stack_driver.h"
|
||||||
|
|
||||||
|
// Forward declaration of sysctl.h, because it's currently declared under headers/private/kernel/sysctl.h
|
||||||
|
int sysctl(int *, uint, void *, size_t *, void *, size_t);
|
||||||
|
|
||||||
|
static char * get_stack_driver_path(void);
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int socket(int family, int type, int protocol)
|
||||||
|
{
|
||||||
|
int sock;
|
||||||
|
int rv;
|
||||||
|
struct stack_driver_args args;
|
||||||
|
|
||||||
|
sock = open(get_stack_driver_path(), O_RDWR);
|
||||||
|
if (sock < 0)
|
||||||
|
return sock;
|
||||||
|
|
||||||
|
args.u.socket.family = family;
|
||||||
|
args.u.socket.type = type;
|
||||||
|
args.u.socket.proto = protocol;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_SOCKET, &args, sizeof(args));
|
||||||
|
if (rv < 0) {
|
||||||
|
close(sock);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int bind(int sock, const struct sockaddr *addr, int addrlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
|
||||||
|
args.u.sockaddr.addr = (struct sockaddr *) addr;
|
||||||
|
args.u.sockaddr.addrlen = addrlen;
|
||||||
|
|
||||||
|
return ioctl(sock, NET_STACK_BIND, &args, sizeof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int shutdown(int sock, int how)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
|
||||||
|
args.u.integer = how;
|
||||||
|
|
||||||
|
return ioctl(sock, NET_STACK_SHUTDOWN, &args, sizeof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int connect(int sock, const struct sockaddr *addr, int addrlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
|
||||||
|
args.u.sockaddr.addr = (struct sockaddr *) addr;
|
||||||
|
args.u.sockaddr.addrlen = addrlen;
|
||||||
|
|
||||||
|
return ioctl(sock, NET_STACK_CONNECT, &args, sizeof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int listen(int sock, int backlog)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
|
||||||
|
args.u.integer = backlog;
|
||||||
|
|
||||||
|
return ioctl(sock, NET_STACK_LISTEN, &args, sizeof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int accept(int sock, struct sockaddr *addr, int *addrlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
int rv;
|
||||||
|
int new_sock;
|
||||||
|
void *cookie;
|
||||||
|
|
||||||
|
new_sock = open(get_stack_driver_path(), O_RDWR);
|
||||||
|
if (new_sock < 0)
|
||||||
|
return new_sock;
|
||||||
|
|
||||||
|
// The network stack driver will need to know to which net_stack_cookie to
|
||||||
|
// *bind* with the new accepted socket. He can't know himself find out
|
||||||
|
// the net_stack_cookie of our new_sock file descriptor, the just open() one...
|
||||||
|
// So, here, we ask him the net_stack_cookie value for our fd... :-)
|
||||||
|
rv = ioctl(new_sock, NET_STACK_GET_COOKIE, &cookie);
|
||||||
|
if (rv < 0) {
|
||||||
|
close(new_sock);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.u.accept.cookie = cookie; // this way driver can use the right fd/cookie for the new_sock!
|
||||||
|
|
||||||
|
args.u.accept.addr = addr;
|
||||||
|
args.u.accept.addrlen = *addrlen;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_ACCEPT, &args, sizeof(args));
|
||||||
|
if (rv < 0) {
|
||||||
|
close(new_sock);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
*addrlen = args.u.accept.addrlen;
|
||||||
|
|
||||||
|
return new_sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT ssize_t recvfrom(int sock, void *buffer, size_t buflen, int flags,
|
||||||
|
struct sockaddr *addr, size_t *addrlen)
|
||||||
|
{
|
||||||
|
struct msghdr mh;
|
||||||
|
struct iovec iov;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
/* XXX - would this be better done as scatter gather? */
|
||||||
|
mh.msg_name = (caddr_t)addr;
|
||||||
|
mh.msg_namelen = addrlen ? *addrlen : 0;
|
||||||
|
mh.msg_flags = flags;
|
||||||
|
mh.msg_control = NULL;
|
||||||
|
mh.msg_controllen = 0;
|
||||||
|
iov.iov_base = buffer;
|
||||||
|
iov.iov_len = buflen;
|
||||||
|
mh.msg_iov = &iov;
|
||||||
|
mh.msg_iovlen = 1;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_RECVFROM, &mh, sizeof(mh));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
if (addrlen)
|
||||||
|
*addrlen = mh.msg_namelen;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
_EXPORT ssize_t sendto(int sock, const void *buffer, size_t buflen, int flags,
|
||||||
|
const struct sockaddr *addr, size_t addrlen)
|
||||||
|
{
|
||||||
|
struct msghdr mh;
|
||||||
|
struct iovec iov;
|
||||||
|
|
||||||
|
/* XXX - would this be better done as scatter gather? */
|
||||||
|
mh.msg_name = (caddr_t)addr;
|
||||||
|
mh.msg_namelen = addrlen;
|
||||||
|
mh.msg_flags = flags;
|
||||||
|
mh.msg_control = NULL;
|
||||||
|
mh.msg_controllen = 0;
|
||||||
|
iov.iov_base = (caddr_t)buffer;
|
||||||
|
iov.iov_len = buflen;
|
||||||
|
mh.msg_iov = &iov;
|
||||||
|
mh.msg_iovlen = 1;
|
||||||
|
|
||||||
|
return ioctl(sock, NET_STACK_SENDTO, &mh, sizeof(mh));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* These need to be adjusted to take account of the MSG_PEEK
|
||||||
|
* flag, but old R5 doesn't use it...
|
||||||
|
*/
|
||||||
|
_EXPORT ssize_t recv(int sock, void *data, size_t datalen, int flags)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
args.u.transfer.data = data;
|
||||||
|
args.u.transfer.datalen = datalen;
|
||||||
|
args.u.transfer.flags = flags;
|
||||||
|
args.u.transfer.addr = NULL;
|
||||||
|
args.u.transfer.addrlen = 0;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_RECV, &args, sizeof(args));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
return args.u.transfer.datalen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT ssize_t send(int sock, const void *data, size_t datalen, int flags)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
args.u.transfer.data = (void *) data;
|
||||||
|
args.u.transfer.datalen = datalen;
|
||||||
|
args.u.transfer.flags = flags;
|
||||||
|
args.u.transfer.addr = NULL;
|
||||||
|
args.u.transfer.addrlen = 0;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_SEND, &args, sizeof(args));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
return args.u.transfer.datalen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int getsockopt(int sock, int level, int option, void *optval, size_t *optlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
args.u.sockopt.level = level;
|
||||||
|
args.u.sockopt.option = option;
|
||||||
|
args.u.sockopt.optval = optval;
|
||||||
|
args.u.sockopt.optlen = *optlen;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_GETSOCKOPT, &args, sizeof(args));
|
||||||
|
if (rv ==0)
|
||||||
|
*optlen = args.u.sockopt.optlen;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int setsockopt(int sock, int level, int option, const void *optval, size_t optlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
|
||||||
|
args.u.sockopt.level = level;
|
||||||
|
args.u.sockopt.option = option;
|
||||||
|
args.u.sockopt.optval = (void *) optval;
|
||||||
|
args.u.sockopt.optlen = optlen;
|
||||||
|
|
||||||
|
return ioctl(sock, NET_STACK_SETSOCKOPT, &args, sizeof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int getpeername(int sock, struct sockaddr *addr, int *addrlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
args.u.sockaddr.addr = addr;
|
||||||
|
args.u.sockaddr.addrlen = *addrlen;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_GETPEERNAME, &args, sizeof(args));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
*addrlen = args.u.sockaddr.addrlen;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
_EXPORT int getsockname(int sock, struct sockaddr *addr, int *addrlen)
|
||||||
|
{
|
||||||
|
struct stack_driver_args args;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
args.u.sockaddr.addr = addr;
|
||||||
|
args.u.sockaddr.addrlen = *addrlen;
|
||||||
|
|
||||||
|
rv = ioctl(sock, NET_STACK_GETSOCKNAME, &args, sizeof(args));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
*addrlen = args.u.sockaddr.addrlen;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
|
_EXPORT int sysctl (int *name, uint namelen, void *oldp, size_t *oldlenp,
|
||||||
|
void *newp, size_t newlen)
|
||||||
|
{
|
||||||
|
int s;
|
||||||
|
struct sysctl_args sa;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
s = socket(PF_ROUTE, SOCK_RAW, 0);
|
||||||
|
if (s < 0)
|
||||||
|
return s;
|
||||||
|
|
||||||
|
sa.name = name;
|
||||||
|
sa.namelen = namelen;
|
||||||
|
sa.oldp = oldp;
|
||||||
|
sa.oldlenp = oldlenp;
|
||||||
|
sa.newp = newp;
|
||||||
|
sa.newlen = newlen;
|
||||||
|
|
||||||
|
rv = ioctl(s, NET_STACK_SYSCTL, &sa, sizeof(sa));
|
||||||
|
|
||||||
|
close(s);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Private routines
|
||||||
|
* ----------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char * get_stack_driver_path(void) {
|
||||||
|
char * path;
|
||||||
|
|
||||||
|
// user-defined stack driver path?
|
||||||
|
path = getenv("NET_STACK_DRIVER_PATH");
|
||||||
|
if (path)
|
||||||
|
return path;
|
||||||
|
|
||||||
|
// use the default stack driver path
|
||||||
|
return NET_STACK_DRIVER_PATH;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user