Merge "Ensure that libbase and liblog can be built for Windows."
diff --git a/adb/Android.mk b/adb/Android.mk
index b34a49f..543f1eb 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -129,6 +129,8 @@
LOCAL_SRC_FILES := \
$(LIBADB_TEST_SRCS) \
$(LIBADB_TEST_linux_SRCS) \
+ shell_service_protocol.cpp \
+ shell_service_protocol_test.cpp \
LOCAL_SANITIZE := $(adb_target_sanitize)
LOCAL_STATIC_LIBRARIES := libadbd
@@ -143,7 +145,12 @@
LOCAL_CFLAGS := -DADB_HOST=1 $(LIBADB_CFLAGS)
LOCAL_CFLAGS_windows := $(LIBADB_windows_CFLAGS)
LOCAL_CFLAGS_linux := $(LIBADB_linux_CFLAGS)
-LOCAL_SRC_FILES := $(LIBADB_TEST_SRCS) services.cpp
+LOCAL_SRC_FILES := \
+ $(LIBADB_TEST_SRCS) \
+ services.cpp \
+ shell_service_protocol.cpp \
+ shell_service_protocol_test.cpp \
+
LOCAL_SRC_FILES_linux := $(LIBADB_TEST_linux_SRCS)
LOCAL_SRC_FILES_darwin := $(LIBADB_TEST_darwin_SRCS)
LOCAL_SANITIZE := $(adb_host_sanitize)
@@ -199,6 +206,7 @@
adb_client.cpp \
services.cpp \
file_sync_client.cpp \
+ shell_service_protocol.cpp \
LOCAL_CFLAGS += \
$(ADB_COMMON_CFLAGS) \
@@ -247,6 +255,7 @@
remount_service.cpp \
set_verity_enable_state_service.cpp \
shell_service.cpp \
+ shell_service_protocol.cpp \
LOCAL_CFLAGS := \
$(ADB_COMMON_CFLAGS) \
diff --git a/adb/shell_service.h b/adb/shell_service.h
index c2a048c..81d7036 100644
--- a/adb/shell_service.h
+++ b/adb/shell_service.h
@@ -14,9 +14,109 @@
* limitations under the License.
*/
+// This file contains classes and functionality to launch shell subprocesses
+// in adbd and communicate between those subprocesses and the adb client.
+//
+// The main features exposed here are:
+// 1. A ShellPacket class to wrap data in a simple protocol. Both adbd and
+// the adb client use this class to transmit data between them.
+// 2. Functions to launch a subprocess on the adbd side.
+
#ifndef SHELL_SERVICE_H_
#define SHELL_SERVICE_H_
+#include <stdint.h>
+
+#include <base/macros.h>
+
+#include "adb.h"
+
+// Class to send and receive shell protocol packets.
+//
+// To keep things simple and predictable, reads and writes block until an entire
+// packet is complete.
+//
+// Example: read raw data from |fd| and send it in a packet.
+// ShellProtocol* p = new ShellProtocol(protocol_fd);
+// int len = adb_read(stdout_fd, p->data(), p->data_capacity());
+// packet->WritePacket(ShellProtocol::kIdStdout, len);
+//
+// Example: read a packet and print it to |stdout|.
+// ShellProtocol* p = new ShellProtocol(protocol_fd);
+// if (p->ReadPacket() && p->id() == kIdStdout) {
+// fwrite(p->data(), 1, p->data_length(), stdout);
+// }
+class ShellProtocol {
+ public:
+ // This is an unscoped enum to make it easier to compare against raw bytes.
+ enum Id : uint8_t {
+ kIdStdin = 0,
+ kIdStdout = 1,
+ kIdStderr = 2,
+ kIdExit = 3,
+ kIdInvalid = 255, // Indicates an invalid or unknown packet.
+ };
+
+ // ShellPackets will probably be too large to allocate on the stack so they
+ // should be dynamically allocated on the heap instead.
+ //
+ // |fd| is an open file descriptor to be used to send or receive packets.
+ explicit ShellProtocol(int fd);
+ virtual ~ShellProtocol();
+
+ // Returns a pointer to the data buffer.
+ const char* data() const { return buffer_ + kHeaderSize; }
+ char* data() { return buffer_ + kHeaderSize; }
+
+ // Returns the total capacity of the data buffer.
+ size_t data_capacity() const { return buffer_end_ - data(); }
+
+ // Reads a packet from the FD.
+ //
+ // If a packet is too big to fit in the buffer then Read() will split the
+ // packet across multiple calls. For example, reading a 50-byte packet into
+ // a 20-byte buffer would read 20 bytes, 20 bytes, then 10 bytes.
+ //
+ // Returns false if the FD closed or errored.
+ bool Read();
+
+ // Returns the ID of the packet in the buffer.
+ int id() const { return buffer_[0]; }
+
+ // Returns the number of bytes that have been read into the data buffer.
+ size_t data_length() const { return data_length_; }
+
+ // Writes the packet currently in the buffer to the FD.
+ //
+ // Returns false if the FD closed or errored.
+ bool Write(Id id, size_t length);
+
+ private:
+ // Packets support 4-byte lengths.
+ typedef uint32_t length_t;
+
+ enum {
+ // It's OK if MAX_PAYLOAD doesn't match on the sending and receiving
+ // end, reading will split larger packets into multiple smaller ones.
+ kBufferSize = MAX_PAYLOAD,
+
+ // Header is 1 byte ID + 4 bytes length.
+ kHeaderSize = sizeof(Id) + sizeof(length_t)
+ };
+
+ int fd_;
+ char buffer_[kBufferSize];
+ size_t data_length_ = 0, bytes_left_ = 0;
+
+ // We need to be able to modify this value for testing purposes, but it
+ // will stay constant during actual program use.
+ char* buffer_end_ = buffer_ + sizeof(buffer_);
+
+ friend class ShellProtocolTest;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellProtocol);
+};
+
#if !ADB_HOST
enum class SubprocessType {
diff --git a/adb/shell_service_protocol.cpp b/adb/shell_service_protocol.cpp
new file mode 100644
index 0000000..623629c
--- /dev/null
+++ b/adb/shell_service_protocol.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 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 "shell_service.h"
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "adb_io.h"
+
+ShellProtocol::ShellProtocol(int fd) : fd_(fd) {
+ buffer_[0] = kIdInvalid;
+}
+
+ShellProtocol::~ShellProtocol() {
+}
+
+bool ShellProtocol::Read() {
+ // Only read a new header if we've finished the last packet.
+ if (!bytes_left_) {
+ if (!ReadFdExactly(fd_, buffer_, kHeaderSize)) {
+ return false;
+ }
+
+ length_t packet_length;
+ memcpy(&packet_length, &buffer_[1], sizeof(packet_length));
+ bytes_left_ = packet_length;
+ data_length_ = 0;
+ }
+
+ size_t read_length = std::min(bytes_left_, data_capacity());
+ if (read_length && !ReadFdExactly(fd_, data(), read_length)) {
+ return false;
+ }
+
+ bytes_left_ -= read_length;
+ data_length_ = read_length;
+
+ return true;
+}
+
+bool ShellProtocol::Write(Id id, size_t length) {
+ buffer_[0] = id;
+ length_t typed_length = length;
+ memcpy(&buffer_[1], &typed_length, sizeof(typed_length));
+
+ return WriteFdExactly(fd_, buffer_, kHeaderSize + length);
+}
diff --git a/adb/shell_service_protocol_test.cpp b/adb/shell_service_protocol_test.cpp
new file mode 100644
index 0000000..85b2f91
--- /dev/null
+++ b/adb/shell_service_protocol_test.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 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 "shell_service.h"
+
+#include <gtest/gtest.h>
+
+#include <signal.h>
+#include <string.h>
+
+#include "sysdeps.h"
+
+class ShellProtocolTest : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+#if !defined(_WIN32)
+ // This is normally done in main.cpp.
+ saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
+#endif
+ }
+
+ static void TearDownTestCase() {
+#if !defined(_WIN32)
+ signal(SIGPIPE, saved_sigpipe_handler_);
+#endif
+ }
+
+ // Initializes the socketpair and ShellProtocols needed for testing.
+ void SetUp() {
+ int fds[2];
+ ASSERT_EQ(0, adb_socketpair(fds));
+ read_fd_ = fds[0];
+ write_fd_ = fds[1];
+
+ write_protocol_ = new ShellProtocol(write_fd_);
+ ASSERT_TRUE(write_protocol_ != nullptr);
+
+ read_protocol_ = new ShellProtocol(read_fd_);
+ ASSERT_TRUE(read_protocol_ != nullptr);
+ }
+
+ // Cleans up FDs and ShellProtocols. If an FD is closed manually during a
+ // test, set it to -1 to prevent TearDown() trying to close it again.
+ void TearDown() {
+ for (int fd : {read_fd_, write_fd_}) {
+ if (fd >= 0) {
+ adb_close(fd);
+ }
+ }
+ for (ShellProtocol* protocol : {read_protocol_, write_protocol_}) {
+ if (protocol) {
+ delete protocol;
+ }
+ }
+ }
+
+ // Fakes the buffer size so we can test filling buffers.
+ void SetReadDataCapacity(size_t size) {
+ read_protocol_->buffer_end_ = read_protocol_->data() + size;
+ }
+
+ static sig_t saved_sigpipe_handler_;
+
+ int read_fd_ = -1, write_fd_ = -1;
+ ShellProtocol *read_protocol_ = nullptr, *write_protocol_ = nullptr;
+};
+
+sig_t ShellProtocolTest::saved_sigpipe_handler_ = nullptr;
+
+namespace {
+
+// Returns true if the packet contains the given values.
+bool PacketEquals(const ShellProtocol* protocol, ShellProtocol::Id id,
+ const void* data, size_t data_length) {
+ return (protocol->id() == id &&
+ protocol->data_length() == data_length &&
+ !memcmp(data, protocol->data(), data_length));
+}
+
+} // namespace
+
+// Tests data that can fit in a single packet.
+TEST_F(ShellProtocolTest, FullPacket) {
+ ShellProtocol::Id id = ShellProtocol::kIdStdout;
+ char data[] = "abc 123 \0\r\n";
+
+ memcpy(write_protocol_->data(), data, sizeof(data));
+ ASSERT_TRUE(write_protocol_->Write(id, sizeof(data)));
+
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_TRUE(PacketEquals(read_protocol_, id, data, sizeof(data)));
+}
+
+// Tests data that has to be read multiple times due to smaller read buffer.
+TEST_F(ShellProtocolTest, ReadBufferOverflow) {
+ ShellProtocol::Id id = ShellProtocol::kIdStdin;
+
+ memcpy(write_protocol_->data(), "1234567890", 10);
+ ASSERT_TRUE(write_protocol_->Write(id, 10));
+
+ SetReadDataCapacity(4);
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_TRUE(PacketEquals(read_protocol_, id, "1234", 4));
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_TRUE(PacketEquals(read_protocol_, id, "5678", 4));
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_TRUE(PacketEquals(read_protocol_, id, "90", 2));
+}
+
+// Tests a zero length packet.
+TEST_F(ShellProtocolTest, ZeroLengthPacket) {
+ ShellProtocol::Id id = ShellProtocol::kIdStderr;
+
+ ASSERT_TRUE(write_protocol_->Write(id, 0));
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_TRUE(PacketEquals(read_protocol_, id, nullptr, 0));
+}
+
+// Tests exit code packets.
+TEST_F(ShellProtocolTest, ExitCodePacket) {
+ write_protocol_->data()[0] = 20;
+ ASSERT_TRUE(write_protocol_->Write(ShellProtocol::kIdExit, 1));
+
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_EQ(ShellProtocol::kIdExit, read_protocol_->id());
+ ASSERT_EQ(20, read_protocol_->data()[0]);
+}
+
+// Tests writing to a closed pipe.
+TEST_F(ShellProtocolTest, WriteToClosedPipeFail) {
+ adb_close(read_fd_);
+ read_fd_ = -1;
+
+ ASSERT_FALSE(write_protocol_->Write(ShellProtocol::kIdStdout, 0));
+}
+
+// Tests writing to a closed FD.
+TEST_F(ShellProtocolTest, WriteToClosedFdFail) {
+ adb_close(write_fd_);
+ write_fd_ = -1;
+
+ ASSERT_FALSE(write_protocol_->Write(ShellProtocol::kIdStdout, 0));
+}
+
+// Tests reading from a closed pipe.
+TEST_F(ShellProtocolTest, ReadFromClosedPipeFail) {
+ adb_close(write_fd_);
+ write_fd_ = -1;
+
+ ASSERT_FALSE(read_protocol_->Read());
+}
+
+// Tests reading from a closed FD.
+TEST_F(ShellProtocolTest, ReadFromClosedFdFail) {
+ adb_close(read_fd_);
+ read_fd_ = -1;
+
+ ASSERT_FALSE(read_protocol_->Read());
+}
+
+// Tests reading from a closed pipe that has a packet waiting. This checks that
+// even if the pipe closes before we can fully read its contents we will still
+// be able to access the last packets.
+TEST_F(ShellProtocolTest, ReadPacketFromClosedPipe) {
+ ShellProtocol::Id id = ShellProtocol::kIdStdout;
+ char data[] = "foo bar";
+
+ memcpy(write_protocol_->data(), data, sizeof(data));
+ ASSERT_TRUE(write_protocol_->Write(id, sizeof(data)));
+ adb_close(write_fd_);
+ write_fd_ = -1;
+
+ // First read should grab the packet.
+ ASSERT_TRUE(read_protocol_->Read());
+ ASSERT_TRUE(PacketEquals(read_protocol_, id, data, sizeof(data)));
+
+ // Second read should fail.
+ ASSERT_FALSE(read_protocol_->Read());
+}
diff --git a/base/Android.mk b/base/Android.mk
index cb04927..ebc2e07 100644
--- a/base/Android.mk
+++ b/base/Android.mk
@@ -26,6 +26,7 @@
libbase_test_src_files := \
file_test.cpp \
logging_test.cpp \
+ parseint_test.cpp \
stringprintf_test.cpp \
strings_test.cpp \
test_main.cpp \
diff --git a/base/include/base/parseint.h b/base/include/base/parseint.h
new file mode 100644
index 0000000..9ecbfbc
--- /dev/null
+++ b/base/include/base/parseint.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef BASE_PARSEINT_H
+#define BASE_PARSEINT_H
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <limits>
+
+namespace android {
+namespace base {
+
+// Parses the unsigned decimal integer in the string 's' and sets 'out' to
+// that value. Optionally allows the caller to define a 'max' beyond which
+// otherwise valid values will be rejected. Returns boolean success.
+template <typename T>
+bool ParseUint(const char* s, T* out,
+ T max = std::numeric_limits<T>::max()) {
+ errno = 0;
+ char* end;
+ unsigned long long int result = strtoull(s, &end, 10);
+ if (errno != 0 || s == end || *end != '\0') {
+ return false;
+ }
+ if (max < result) {
+ return false;
+ }
+ *out = static_cast<T>(result);
+ return true;
+}
+
+// Parses the signed decimal integer in the string 's' and sets 'out' to
+// that value. Optionally allows the caller to define a 'min' and 'max
+// beyond which otherwise valid values will be rejected. Returns boolean
+// success.
+template <typename T>
+bool ParseInt(const char* s, T* out,
+ T min = std::numeric_limits<T>::min(),
+ T max = std::numeric_limits<T>::max()) {
+ errno = 0;
+ char* end;
+ long long int result = strtoll(s, &end, 10);
+ if (errno != 0 || s == end || *end != '\0') {
+ return false;
+ }
+ if (result < min || max < result) {
+ return false;
+ }
+ *out = static_cast<T>(result);
+ return true;
+}
+
+} // namespace base
+} // namespace android
+
+#endif // BASE_PARSEINT_H
diff --git a/base/parseint_test.cpp b/base/parseint_test.cpp
new file mode 100644
index 0000000..6a109f4
--- /dev/null
+++ b/base/parseint_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 "base/parseint.h"
+
+#include <gtest/gtest.h>
+
+TEST(parseint, signed_smoke) {
+ int i;
+ ASSERT_EQ(false, android::base::ParseInt("x", &i));
+ ASSERT_EQ(false, android::base::ParseInt("123x", &i));
+
+ ASSERT_EQ(true, android::base::ParseInt("123", &i));
+ ASSERT_EQ(123, i);
+ ASSERT_EQ(true, android::base::ParseInt("-123", &i));
+ ASSERT_EQ(-123, i);
+
+ short s;
+ ASSERT_EQ(true, android::base::ParseInt("1234", &s));
+ ASSERT_EQ(1234, s);
+
+ ASSERT_EQ(true, android::base::ParseInt("12", &i, 0, 15));
+ ASSERT_EQ(12, i);
+ ASSERT_EQ(false, android::base::ParseInt("-12", &i, 0, 15));
+ ASSERT_EQ(false, android::base::ParseInt("16", &i, 0, 15));
+}
+
+TEST(parseint, unsigned_smoke) {
+ unsigned int i;
+ ASSERT_EQ(false, android::base::ParseUint("x", &i));
+ ASSERT_EQ(false, android::base::ParseUint("123x", &i));
+
+ ASSERT_EQ(true, android::base::ParseUint("123", &i));
+ ASSERT_EQ(123u, i);
+ ASSERT_EQ(false, android::base::ParseUint("-123", &i));
+
+ unsigned short s;
+ ASSERT_EQ(true, android::base::ParseUint("1234", &s));
+ ASSERT_EQ(1234u, s);
+
+ ASSERT_EQ(true, android::base::ParseUint("12", &i, 15u));
+ ASSERT_EQ(12u, i);
+ ASSERT_EQ(false, android::base::ParseUint("-12", &i, 15u));
+ ASSERT_EQ(false, android::base::ParseUint("16", &i, 15u));
+}
+
+TEST(parseint, no_implicit_octal) {
+ int i;
+ ASSERT_EQ(true, android::base::ParseInt("0123", &i));
+ ASSERT_EQ(123, i);
+
+ unsigned int u;
+ ASSERT_EQ(true, android::base::ParseUint("0123", &u));
+ ASSERT_EQ(123u, u);
+}
diff --git a/toolbox/Android.mk b/toolbox/Android.mk
index 1d3605e..f4a03fe 100644
--- a/toolbox/Android.mk
+++ b/toolbox/Android.mk
@@ -43,7 +43,6 @@
iftop \
ioctl \
log \
- lsof \
nandread \
newfs_msdos \
ps \
diff --git a/toolbox/lsof.c b/toolbox/lsof.c
deleted file mode 100644
index 198ca52..0000000
--- a/toolbox/lsof.c
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (c) 2010, The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <ctype.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <libgen.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <pwd.h>
-#include <sys/stat.h>
-
-#define BUF_MAX 1024
-#define CMD_DISPLAY_MAX (9 + 1)
-#define USER_DISPLAY_MAX (10 + 1)
-
-struct pid_info_t {
- pid_t pid;
- char user[USER_DISPLAY_MAX];
-
- char cmdline[CMD_DISPLAY_MAX];
-
- char path[PATH_MAX];
- ssize_t parent_length;
-};
-
-static void print_header()
-{
- printf("%-9s %5s %10s %4s %9s %18s %9s %10s %s\n",
- "COMMAND",
- "PID",
- "USER",
- "FD",
- "TYPE",
- "DEVICE",
- "SIZE/OFF",
- "NODE",
- "NAME");
-}
-
-static void print_symlink(const char* name, const char* path, struct pid_info_t* info)
-{
- static ssize_t link_dest_size;
- static char link_dest[PATH_MAX];
-
- strlcat(info->path, path, sizeof(info->path));
- if ((link_dest_size = readlink(info->path, link_dest, sizeof(link_dest)-1)) < 0) {
- if (errno == ENOENT)
- goto out;
-
- snprintf(link_dest, sizeof(link_dest), "%s (readlink: %s)", info->path, strerror(errno));
- } else {
- link_dest[link_dest_size] = '\0';
- }
-
- // Things that are just the root filesystem are uninteresting (we already know)
- if (!strcmp(link_dest, "/"))
- goto out;
-
- const char* fd = name;
- char rw = ' ';
- char locks = ' '; // TODO: read /proc/locks
-
- const char* type = "unknown";
- char device[32] = "?";
- char size_off[32] = "?";
- char node[32] = "?";
-
- struct stat sb;
- if (lstat(link_dest, &sb) != -1) {
- switch ((sb.st_mode & S_IFMT)) {
- case S_IFSOCK: type = "sock"; break; // TODO: what domain?
- case S_IFLNK: type = "LINK"; break;
- case S_IFREG: type = "REG"; break;
- case S_IFBLK: type = "BLK"; break;
- case S_IFDIR: type = "DIR"; break;
- case S_IFCHR: type = "CHR"; break;
- case S_IFIFO: type = "FIFO"; break;
- }
- snprintf(device, sizeof(device), "%d,%d", (int) sb.st_dev, (int) sb.st_rdev);
- snprintf(node, sizeof(node), "%d", (int) sb.st_ino);
- snprintf(size_off, sizeof(size_off), "%d", (int) sb.st_size);
- }
-
- if (!name) {
- // We're looking at an fd, so read its flags.
- fd = path;
- char fdinfo_path[PATH_MAX];
- snprintf(fdinfo_path, sizeof(fdinfo_path), "/proc/%d/fdinfo/%s", info->pid, path);
- FILE* fp = fopen(fdinfo_path, "r");
- if (fp != NULL) {
- int pos;
- unsigned flags;
-
- if (fscanf(fp, "pos: %d flags: %o", &pos, &flags) == 2) {
- flags &= O_ACCMODE;
- if (flags == O_RDONLY) rw = 'r';
- else if (flags == O_WRONLY) rw = 'w';
- else rw = 'u';
- }
- fclose(fp);
- }
- }
-
- printf("%-9s %5d %10s %4s%c%c %9s %18s %9s %10s %s\n",
- info->cmdline, info->pid, info->user, fd, rw, locks, type, device, size_off, node, link_dest);
-
-out:
- info->path[info->parent_length] = '\0';
-}
-
-// Prints out all file that have been memory mapped
-static void print_maps(struct pid_info_t* info)
-{
- FILE *maps;
- size_t offset;
- char device[10];
- long int inode;
- char file[PATH_MAX];
-
- strlcat(info->path, "maps", sizeof(info->path));
-
- maps = fopen(info->path, "r");
- if (!maps)
- goto out;
-
- while (fscanf(maps, "%*x-%*x %*s %zx %s %ld %s\n", &offset, device, &inode, file) == 4) {
- // We don't care about non-file maps
- if (inode == 0 || !strcmp(device, "00:00"))
- continue;
-
- printf("%-9s %5d %10s %4s %9s %18s %9zd %10ld %s\n",
- info->cmdline, info->pid, info->user, "mem",
- "REG", device, offset, inode, file);
- }
-
- fclose(maps);
-
-out:
- info->path[info->parent_length] = '\0';
-}
-
-// Prints out all open file descriptors
-static void print_fds(struct pid_info_t* info)
-{
- static char* fd_path = "fd/";
- strlcat(info->path, fd_path, sizeof(info->path));
-
- int previous_length = info->parent_length;
- info->parent_length += strlen(fd_path);
-
- DIR *dir = opendir(info->path);
- if (dir == NULL) {
- char msg[BUF_MAX];
- snprintf(msg, sizeof(msg), "%s (opendir: %s)", info->path, strerror(errno));
- printf("%-9s %5d %10s %4s %9s %18s %9s %10s %s\n",
- info->cmdline, info->pid, info->user, "FDS",
- "", "", "", "", msg);
- goto out;
- }
-
- struct dirent* de;
- while ((de = readdir(dir))) {
- if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
- continue;
-
- print_symlink(NULL, de->d_name, info);
- }
- closedir(dir);
-
-out:
- info->parent_length = previous_length;
- info->path[info->parent_length] = '\0';
-}
-
-static void lsof_dumpinfo(pid_t pid)
-{
- int fd;
- struct pid_info_t info;
- struct stat pidstat;
- struct passwd *pw;
-
- info.pid = pid;
- snprintf(info.path, sizeof(info.path), "/proc/%d/", pid);
- info.parent_length = strlen(info.path);
-
- // Get the UID by calling stat on the proc/pid directory.
- if (!stat(info.path, &pidstat)) {
- pw = getpwuid(pidstat.st_uid);
- if (pw) {
- strlcpy(info.user, pw->pw_name, sizeof(info.user));
- } else {
- snprintf(info.user, USER_DISPLAY_MAX, "%d", (int)pidstat.st_uid);
- }
- } else {
- strcpy(info.user, "???");
- }
-
- // Read the command line information; each argument is terminated with NULL.
- strlcat(info.path, "cmdline", sizeof(info.path));
- fd = open(info.path, O_RDONLY);
- if (fd < 0) {
- fprintf(stderr, "Couldn't read %s\n", info.path);
- return;
- }
-
- char cmdline[PATH_MAX];
- int numRead = read(fd, cmdline, sizeof(cmdline) - 1);
- close(fd);
-
- if (numRead < 0) {
- fprintf(stderr, "Error reading cmdline: %s: %s\n", info.path, strerror(errno));
- return;
- }
-
- cmdline[numRead] = '\0';
-
- // We only want the basename of the cmdline
- strlcpy(info.cmdline, basename(cmdline), sizeof(info.cmdline));
-
- // Read each of these symlinks
- print_symlink("cwd", "cwd", &info);
- print_symlink("txt", "exe", &info);
- print_symlink("rtd", "root", &info);
- print_fds(&info);
- print_maps(&info);
-}
-
-int lsof_main(int argc, char *argv[])
-{
- long int pid = 0;
- char* endptr;
- if (argc == 2) {
- pid = strtol(argv[1], &endptr, 10);
- }
-
- print_header();
-
- if (pid) {
- lsof_dumpinfo(pid);
- } else {
- DIR *dir = opendir("/proc");
- if (dir == NULL) {
- fprintf(stderr, "Couldn't open /proc\n");
- return -1;
- }
-
- struct dirent* de;
- while ((de = readdir(dir))) {
- if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
- continue;
-
- // Only inspect directories that are PID numbers
- pid = strtol(de->d_name, &endptr, 10);
- if (*endptr != '\0')
- continue;
-
- lsof_dumpinfo(pid);
- }
- closedir(dir);
- }
-
- return 0;
-}