/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#include "ConnectionDetector.h"
#include <utils/Log.h>
#include <dirent.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sstream>
namespace android {
namespace SensorHalExt {
// SocketConnectionDetector functions
SocketConnectionDetector::SocketConnectionDetector(BaseDynamicSensorDaemon *d, int port)
: ConnectionDetector(d), Thread(false /*canCallJava*/) {
// initialize socket that accept connection to localhost:port
mListenFd = ::socket(AF_INET, SOCK_STREAM, 0);
if (mListenFd < 0) {
ALOGE("Cannot open socket");
return;
}
struct sockaddr_in serverAddress = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {
.s_addr = htonl(INADDR_LOOPBACK)
}
};
::bind(mListenFd, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
if (::listen(mListenFd, 0) != NO_ERROR) {
ALOGE("Cannot listen to port %d", port);
mListenFd = -1;
return;
}
std::ostringstream s;
s << "socket:" << port;
mDevice = s.str();
run("ddad_socket");
}
SocketConnectionDetector::~SocketConnectionDetector() {
if (mListenFd >= 0) {
requestExitAndWait();
}
}
int SocketConnectionDetector::waitForConnection() {
return ::accept(mListenFd, nullptr, nullptr);
}
void SocketConnectionDetector::waitForDisconnection(int connFd) {
char buffer[16];
while (::read(connFd, buffer, sizeof(buffer)) > 0) {
// discard data but response something to denote thread alive
::write(connFd, ".", 1);
}
// read failure means disconnection
::close(connFd);
}
bool SocketConnectionDetector::threadLoop() {
while (!Thread::exitPending()) {
// block waiting for connection
int connFd = waitForConnection();
if (connFd < 0) {
break;
}
ALOGV("Received connection, register dynamic accel sensor");
mDaemon->onConnectionChange(mDevice, true);
waitForDisconnection(connFd);
ALOGV("Connection break, unregister dynamic accel sensor");
mDaemon->onConnectionChange(mDevice, false);
}
mDaemon->onConnectionChange(mDevice, false);
ALOGD("SocketConnectionDetector thread exited");
return false;
}
// FileConnectionDetector functions
FileConnectionDetector::FileConnectionDetector (
BaseDynamicSensorDaemon *d, const std::string &path, const std::string ®ex)
: ConnectionDetector(d), Thread(false /*callCallJava*/), mPath(path), mRegex(regex),
mLooper(new Looper(true /*allowNonCallback*/)), mInotifyFd(-1) {
if (mLooper == nullptr) {
return;
}
mInotifyFd = ::inotify_init1(IN_NONBLOCK);
if (mInotifyFd < 0) {
ALOGE("Cannot init inotify");
return;
}
int wd = ::inotify_add_watch(mInotifyFd, path.c_str(), IN_CREATE | IN_DELETE);
if (wd < 0 || !mLooper->addFd(mInotifyFd, POLL_IDENT, Looper::EVENT_INPUT, nullptr, nullptr)) {
::close(mInotifyFd);
mInotifyFd = -1;
ALOGE("Cannot setup watch on dir %s", path.c_str());
return;
}
// mLooper != null && mInotifyFd added to looper
run("ddad_file");
}
FileConnectionDetector::~FileConnectionDetector() {
if (mInotifyFd > 0) {
requestExit();
mLooper->wake();
join();
::close(mInotifyFd);
}
}
bool FileConnectionDetector::matches(const std::string &name) const {
return std::regex_match(name, mRegex);
}
std::string FileConnectionDetector::getFullName(const std::string name) const {
return mPath + name;
}
void FileConnectionDetector::processExistingFiles() const {
auto dirp = ::opendir(mPath.c_str());
struct dirent *dp;
while ((dp = ::readdir(dirp)) != NULL) {
const std::string name(dp->d_name);
if (matches(name)) {
mDaemon->onConnectionChange(getFullName(name), true /*connected*/);
}
}
::closedir(dirp);
}
void FileConnectionDetector::handleInotifyData(ssize_t len, const char *data) {
const char *dataEnd = data + len;
const struct inotify_event *ev;
// inotify adds paddings to guarantee the next read is aligned
for (; data < dataEnd; data += sizeof(struct inotify_event) + ev->len) {
ev = reinterpret_cast<const struct inotify_event*>(data);
if (ev->mask & IN_ISDIR) {
continue;
}
const std::string name(ev->name);
if (matches(name)) {
if (ev->mask & IN_CREATE) {
mDaemon->onConnectionChange(getFullName(name), true /*connected*/);
}
if (ev->mask & IN_DELETE) {
mDaemon->onConnectionChange(getFullName(name), false /*connected*/);
}
}
}
}
bool FileConnectionDetector::readInotifyData() {
struct {
struct inotify_event ev;
char padding[NAME_MAX + 1];
} buffer;
bool ret = true;
while (true) {
ssize_t len = ::read(mInotifyFd, &buffer, sizeof(buffer));
if (len == -1 && errno == EAGAIN) {
// no more data
break;
} else if (len > static_cast<ssize_t>(sizeof(struct inotify_event))) {
handleInotifyData(len, reinterpret_cast<char*>(&buffer));
} else if (len < 0) {
ALOGE("read error: %s", ::strerror(errno));
ret = false;
break;
} else {
// 0 <= len <= sizeof(struct inotify_event)
ALOGE("read return %zd, shorter than inotify_event size %zu",
len, sizeof(struct inotify_event));
ret = false;
break;
}
}
return ret;
}
bool FileConnectionDetector::threadLoop() {
Looper::setForThread(mLooper);
processExistingFiles();
while(!Thread::exitPending()) {
int ret = mLooper->pollOnce(-1);
if (ret != Looper::POLL_WAKE && ret != POLL_IDENT) {
ALOGE("Unexpected value %d from pollOnce, quit", ret);
requestExit();
break;
}
if (ret == POLL_IDENT) {
if (!readInotifyData()) {
requestExit();
}
}
}
mLooper->removeFd(mInotifyFd);
ALOGD("FileConnectionDetection thread exited");
return false;
}
} // namespace SensorHalExt
} // namespace android