blob: efc1882893dddd3d6e22846683b105c20b21ffa1 [file] [edit]
/*
*
* Copyright (c) 2016 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Description:
* This file defindes the SuperSocket class, which provides an
* easy way to create various types of sockets using a convenient
* path syntax.
*
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include "assert-macros.h"
#include "SuperSocket.h"
#include "socket-utils.h"
#include "time-utils.h"
#include <syslog.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <sys/file.h>
#include "../wpantund/NCPConstants.h"
#if HAVE_LIBUDEV
#include <libudev.h>
#endif
using namespace nl;
SuperSocket::SuperSocket(const std::string& path)
:UnixSocket(open_super_socket(path.c_str()), false /*should_close*/), mPath(path)
{
// Note that the "should_close" flag above is set to false.
// This is because a super-socket file descriptor must be closed
// with "close_super_socket()". As such, we handle closing the
// socket ourselves in our destructor.
if (0 > mFDRead) {
syslog(LOG_ERR, "Unable to open socket with path <%s>, errno=%d (%s)", path.c_str(), errno, strerror(errno));
throw SocketError("Unable to open socket");
}
// Lock the file descriptor if it is to a device. It does not make sense
// to allow someone else to use this file descriptor at the same time,
// or to use the device while someone else is using it.
if ( (SUPER_SOCKET_TYPE_DEVICE == get_super_socket_type_from_path(path.c_str()))
&& (flock(mFDRead, LOCK_EX|LOCK_NB) < 0)
) {
// The only error we care about is EWOULDBLOCK. EINVAL is fine,
// it just means this file descriptor doesn't support locking.
if (EWOULDBLOCK == errno) {
syslog(LOG_ERR, "Socket \"%s\" is locked by another process", path.c_str());
// Failure to close this super socket here was
// the initiating cause of oss-fuzz-3565
close_super_socket(mFDRead);
throw SocketError("Socket is locked by another process");
}
}
}
SuperSocket::~SuperSocket()
{
SuperSocket::hibernate();
}
boost::shared_ptr<SocketWrapper>
SuperSocket::create(const std::string& path)
{
return boost::shared_ptr<SocketWrapper>(new SuperSocket(path));
}
int
SuperSocket::hibernate(void)
{
if (mFDRead >= 0) {
// Unlock the FD.
IGNORE_RETURN_VALUE(flock(mFDRead, LOCK_UN));
// Close the existing FD.
IGNORE_RETURN_VALUE(close_super_socket(mFDRead));
}
mFDRead = -1;
mFDWrite = -1;
return 0;
}
void SuperSocket::waitForDevice()
{
#if HAVE_LIBUDEV
int fd;
cms_t end;
cms_t now;
struct udev_monitor *mon;
struct udev *udev = udev_new();
if (!udev) {
syslog(LOG_ERR, "Can't create udev");
return;
}
mon = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(mon, "tty", NULL);
udev_monitor_enable_receiving(mon);
fd = udev_monitor_get_fd(mon);
end = time_ms() + (cms_t)(NCP_RESET_TIMEOUT * MSEC_PER_SEC);
do {
int ret;
fd_set fds;
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100 * USEC_PER_MSEC;
FD_ZERO(&fds);
FD_SET(fd, &fds);
ret = select(fd + 1, &fds, NULL, NULL, &tv);
if (ret > 0 && FD_ISSET(fd, &fds)) {
struct udev_device *dev = udev_monitor_receive_device(mon);
if (dev) {
struct udev_list_entry *entry;
const char *action = udev_device_get_action(dev);
if (strncmp(action, "add", strlen("add")) == 0) {
udev_list_entry_foreach(
entry, udev_device_get_devlinks_list_entry(dev))
{
const char *name = udev_list_entry_get_name(entry);
if (strncmp(name, mPath.c_str(), mPath.length()) ==
0) {
udev_device_unref(dev);
goto exit;
}
}
}
udev_device_unref(dev);
}
}
now = time_ms();
} while (end > now);
syslog(LOG_WARNING, "The USB CDC ACM device has not been added in desired time");
exit:
udev_unref(udev);
#endif
}
void
SuperSocket::reset()
{
syslog(LOG_DEBUG, "SuperSocket::reset()");
#if !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
SuperSocket::waitForDevice();
SuperSocket::hibernate();
// Sleep for 200ms to wait for things to settle down.
usleep(MSEC_PER_SEC * 200);
mFDRead = mFDWrite = open_super_socket(mPath.c_str());
if (mFDRead < 0) {
// Unable to reopen socket...!
syslog(LOG_ERR, "SuperSocket::Reset: Unable to reopen socket <%s>, errno=%d (%s)", mPath.c_str(), errno, strerror(errno));
throw SocketError("Unable to reopen socket");
}
// Lock the file descriptor. It does not make sense to allow someone else
// to use this file descriptor at the same time, or to use the device
// while someone else is using it.
if ( (SUPER_SOCKET_TYPE_DEVICE == get_super_socket_type_from_path(mPath.c_str()))
&& (flock(mFDRead, LOCK_EX|LOCK_NB) < 0)
) {
// The only error we care about is EWOULDBLOCK. EINVAL is fine,
// it just means this file descriptor doesn't support locking.
if (EWOULDBLOCK == errno) {
syslog(LOG_ERR, "Socket is locked by another process");
throw SocketError("Socket is locked by another process");
}
}
#endif // if !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
}