]> git.sven.stormbind.net Git - sven/jattach.git/commitdiff
Import Upstream version 1.5 upstream/1.5
authorSven Höxter <sven.hoexter@rewe-digital.com>
Thu, 22 Aug 2019 15:46:34 +0000 (17:46 +0200)
committerSven Höxter <sven.hoexter@rewe-digital.com>
Thu, 22 Aug 2019 15:46:34 +0000 (17:46 +0200)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
jattach.spec [new file with mode: 0644]
src/jattach_posix.c [new file with mode: 0644]
src/jattach_windows.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..495a75e
--- /dev/null
@@ -0,0 +1,2 @@
+/build/
+
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..8fd6d4d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,59 @@
+JATTACH_VERSION=1.5
+
+ifneq ($(findstring Windows,$(OS)),)
+  CL=cl.exe
+  CFLAGS=/O2 /D_CRT_SECURE_NO_WARNINGS
+  JATTACH_EXE=jattach.exe
+else 
+  UNAME_S:=$(shell uname -s)
+  ifneq ($(findstring FreeBSD,$(UNAME_S)),)
+    CC=cc
+    CFLAGS=-O2
+    JATTACH_EXE=jattach
+  else
+    ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+    RPM_ROOT=$(ROOT_DIR)/build/rpm
+    SOURCES=$(RPM_ROOT)/SOURCES
+    SPEC_FILE=jattach.spec
+    CC=gcc
+    CFLAGS=-O2
+    JATTACH_EXE=jattach
+  endif
+endif
+
+all: build build/$(JATTACH_EXE)
+
+build:
+       mkdir -p build
+
+build/jattach: src/jattach_posix.c
+       $(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
+
+build/jattach.exe: src/jattach_windows.c
+       $(CL) $(CFLAGS) /DJATTACH_VERSION=\"$(JATTACH_VERSION)\" /Fobuild/jattach.obj /Fe$@ $^ advapi32.lib /link /SUBSYSTEM:CONSOLE,5.02
+
+clean:
+       rm -rf build
+
+$(RPM_ROOT):
+       mkdir -p $(RPM_ROOT)
+
+rpm-dirs: $(RPM_ROOT)
+       mkdir -p $(RPM_ROOT)/SPECS
+       mkdir -p $(SOURCES)/bin
+       mkdir -p $(RPM_ROOT)/BUILD
+       mkdir -p $(RPM_ROOT)/SRPMS
+       mkdir -p $(RPM_ROOT)/RPMS
+       mkdir -p $(RPM_ROOT)/tmp
+
+rpm: rpm-dirs build build/$(JATTACH_EXE)
+       cp $(SPEC_FILE) $(RPM_ROOT)/
+       cp build/jattach $(SOURCES)/bin/
+       rpmbuild -bb \
+                --define '_topdir $(RPM_ROOT)' \
+                --define '_tmppath $(RPM_ROOT)/tmp' \
+                --clean \
+                --rmsource \
+                --rmspec \
+                --buildroot $(RPM_ROOT)/tmp/build-root \
+                $(RPM_ROOT)/jattach.spec
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..7532b56
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+## jattach
+
+### JVM Dynamic Attach utility
+
+The utility to send commands to remote JVM via Dynamic Attach mechanism.
+
+All-in-one **jmap + jstack + jcmd + jinfo** functionality in a single tiny program.  
+No installed JDK required, works with just JRE. Supports Linux containers.
+
+This is the lightweight native version of HotSpot Attach API  
+https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/
+
+[Supported commands](http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/812ed44725b8/src/share/vm/services/attachListener.cpp#l388):
+ - **load**            : load agent library
+ - **properties**      : print system properties
+ - **agentProperties** : print agent properties
+ - **datadump**        : show heap and thread summary
+ - **threaddump**      : dump all stack traces (like jstack)
+ - **dumpheap**        : dump heap (like jmap)
+ - **inspectheap**     : heap histogram (like jmap -histo)
+ - **setflag**         : modify manageable VM flag
+ - **printflag**       : print VM flag
+ - **jcmd**            : execute jcmd command
+
+### Examples
+#### Load JVMTI agent
+
+    $ jattach <pid> load <.so-path> { true | false } [ options ]
+
+Where `true` means that the path is absolute, `false` -- the path is relative.
+
+`options` are passed to the agent.
+
+#### List available jcmd commands 
+
+    $ jattach <pid> jcmd "help -all"
+
+### Installation
+#### FreeBSD
+
+On FreeBSD, you can use the following command to install `jattach` package:
+
+    $ pkg install jattach
+
+#### Alpine Linux
+
+On Alpine Linux, you can use the following command to install `jattach` package from the edge/testing repository:
+
+    $ apk add --no-cache jattach --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/
+
+#### Archlinux
+
+[jattach](https://aur.archlinux.org/packages/jattach/) package can be installed from [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository) using one of [AUR helpers](https://wiki.archlinux.org/index.php/AUR_helpers), e.g., `yay`:
+
+    $ yay -S jattach
diff --git a/jattach.spec b/jattach.spec
new file mode 100644 (file)
index 0000000..92dff2e
--- /dev/null
@@ -0,0 +1,39 @@
+Name:          jattach
+Version:       1.3
+Release:       1
+Summary:       JVM Dynamic Attach utility
+
+Group:         Development/Tools
+License:       ASL 2.0
+URL:           https://github.com/apangin/jattach
+Vendor:                Andrei Pangin
+Packager:      Vadim Tsesko <incubos@yandex.com>
+
+BuildRequires: gcc
+BuildRequires: make
+
+%description
+The utility to send commands to remote JVM via Dynamic Attach mechanism.
+
+All-in-one jmap + jstack + jcmd + jinfo functionality in a single tiny program.
+No installed JDK required, works with just JRE.
+
+This is the lightweight native version of HotSpot Attach API:
+https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/
+
+%build
+# Do nothing
+
+%install
+BIN=%{buildroot}/usr/bin
+
+mkdir -p ${BIN}
+
+install -p -m 555 %{_sourcedir}/bin/jattach ${BIN}
+
+%files
+/usr/bin/jattach
+
+%changelog
+* Wed Nov 30 2016 Vadim Tsesko <incubos@yandex.com> - 0.1-1
+- Initial version
diff --git a/src/jattach_posix.c b/src/jattach_posix.c
new file mode 100644 (file)
index 0000000..cdf7821
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2016 Andrei Pangin
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/syscall.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAX_PATH 1024
+#define TMP_PATH (MAX_PATH - 64)
+
+static char temp_path_storage[TMP_PATH] = {0};
+
+
+#ifdef __linux__
+
+const char* get_temp_path() {
+    return temp_path_storage;
+}
+
+int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
+    // A process may have its own root path (when running in chroot environment)
+    char path[64];
+    snprintf(path, sizeof(path), "/proc/%d/root", pid);
+
+    // Append /tmp to the resolved root symlink
+    ssize_t path_size = readlink(path, temp_path_storage, sizeof(temp_path_storage) - 10);
+    strcpy(temp_path_storage + (path_size > 1 ? path_size : 0), "/tmp");
+
+    // Parse /proc/pid/status to find process credentials
+    snprintf(path, sizeof(path), "/proc/%d/status", pid);
+    FILE* status_file = fopen(path, "r");
+    if (status_file == NULL) {
+        return 0;
+    }
+
+    char* line = NULL;
+    size_t size;
+
+    while (getline(&line, &size, status_file) != -1) {
+        if (strncmp(line, "Uid:", 4) == 0) {
+            // Get the effective UID, which is the second value in the line
+            *uid = (uid_t)atoi(strchr(line + 5, '\t'));
+        } else if (strncmp(line, "Gid:", 4) == 0) {
+            // Get the effective GID, which is the second value in the line
+            *gid = (gid_t)atoi(strchr(line + 5, '\t'));
+        } else if (strncmp(line, "NStgid:", 7) == 0) {
+            // PID namespaces can be nested; the last one is the innermost one
+            *nspid = atoi(strrchr(line, '\t'));
+        }
+    }
+
+    free(line);
+    fclose(status_file);
+    return 1;
+}
+
+int enter_mount_ns(int pid) {
+#ifdef __NR_setns
+    char path[128];
+    snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
+
+    struct stat oldns_stat, newns_stat;
+    if (stat("/proc/self/ns/mnt", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
+        // Don't try to call setns() if we're in the same namespace already
+        if (oldns_stat.st_ino != newns_stat.st_ino) {
+            int newns = open(path, O_RDONLY);
+            if (newns < 0) {
+                return 0;
+            }
+
+            // Some ancient Linux distributions do not have setns() function
+            int result = syscall(__NR_setns, newns, 0);
+            close(newns);
+            return result < 0 ? 0 : 1;
+        }
+    }
+#endif // __NR_setns
+
+    return 1;
+}
+
+// The first line of /proc/pid/sched looks like
+// java (1234, #threads: 12)
+// where 1234 is the required host PID
+int sched_get_host_pid(const char* path) {
+    static char* line = NULL;
+    size_t size;
+    int result = -1;
+
+    FILE* sched_file = fopen(path, "r");
+    if (sched_file != NULL) {
+        if (getline(&line, &size, sched_file) != -1) {
+            char* c = strrchr(line, '(');
+            if (c != NULL) {
+                result = atoi(c + 1);
+            }
+        }
+        fclose(sched_file);
+    }
+
+    return result;
+}
+
+// Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
+// Fortunately, /proc/pid/sched in a container exposes a host PID,
+// so the idea is to scan all container PIDs to find which one matches the host PID.
+int alt_lookup_nspid(int pid) {
+    int namespace_differs = 0;
+    char path[300];
+    snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
+
+    // Don't bother looking for container PID if we are already in the same PID namespace
+    struct stat oldns_stat, newns_stat;
+    if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
+        if (oldns_stat.st_ino == newns_stat.st_ino) {
+            return pid;
+        }
+        namespace_differs = 1;
+    }
+
+    // Otherwise browse all PIDs in the namespace of the target process
+    // trying to find which one corresponds to the host PID
+    snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
+    DIR* dir = opendir(path);
+    if (dir != NULL) {
+        struct dirent* entry;
+        while ((entry = readdir(dir)) != NULL) {
+            if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
+                // Check if /proc/<container-pid>/sched points back to <host-pid>
+                snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
+                if (sched_get_host_pid(path) == pid) {
+                    closedir(dir);
+                    return atoi(entry->d_name);
+                }
+            }
+        }
+        closedir(dir);
+    }
+
+    if (namespace_differs) {
+        printf("WARNING: couldn't find container pid of the target process\n");
+    }
+
+    return pid;
+}
+
+#elif defined(__APPLE__)
+
+#include <sys/sysctl.h>
+
+// macOS has a secure per-user temporary directory
+const char* get_temp_path() {
+    if (temp_path_storage[0] == 0) {
+        int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, temp_path_storage, sizeof(temp_path_storage));
+        if (path_size == 0 || path_size > sizeof(temp_path_storage)) {
+            strcpy(temp_path_storage, "/tmp");
+        }
+    }
+
+    return temp_path_storage;
+}
+
+int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
+    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+    struct kinfo_proc info;
+    size_t len = sizeof(info);
+
+    if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
+        return 0;
+    }
+
+    *uid = info.kp_eproc.e_ucred.cr_uid;
+    *gid = info.kp_eproc.e_ucred.cr_gid;
+    *nspid = pid;
+    return 1;
+}
+
+// This is a Linux-specific API; nothing to do on macOS and FreeBSD
+int enter_mount_ns(int pid) {
+    return 1;
+}
+
+// Not used on macOS and FreeBSD
+int alt_lookup_nspid(int pid) {
+    return pid;
+}
+
+#else // __FreeBSD__
+
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+const char* get_temp_path() {
+    return "/tmp";
+}
+
+int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
+    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+    struct kinfo_proc info;
+    size_t len = sizeof(info);
+
+    if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
+        return 0;
+    }
+
+    *uid = info.ki_uid;
+    *gid = info.ki_groups[0];
+    *nspid = pid;
+    return 1;
+}
+
+// This is a Linux-specific API; nothing to do on macOS and FreeBSD
+int enter_mount_ns(int pid) {
+    return 1;
+}
+
+// Not used on macOS and FreeBSD
+int alt_lookup_nspid(int pid) {
+    return pid;
+}
+
+#endif
+
+
+// Check if remote JVM has already opened socket for Dynamic Attach
+static int check_socket(int pid) {
+    char path[MAX_PATH];
+    snprintf(path, sizeof(path), "%s/.java_pid%d", get_temp_path(), pid);
+
+    struct stat stats;
+    return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
+}
+
+// Check if a file is owned by current user
+static int check_file_owner(const char* path) {
+    struct stat stats;
+    if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
+        return 1;
+    }
+
+    // Some mounted filesystems may change the ownership of the file.
+    // JVM will not trust such file, so it's better to remove it and try a different path
+    unlink(path);
+    return 0;
+}
+
+// Force remote JVM to start Attach listener.
+// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
+static int start_attach_mechanism(int pid, int nspid) {
+    char path[MAX_PATH];
+    snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
+    
+    int fd = creat(path, 0660);
+    if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
+        // Failed to create attach trigger in current directory. Retry in /tmp
+        snprintf(path, sizeof(path), "%s/.attach_pid%d", get_temp_path(), nspid);
+        fd = creat(path, 0660);
+        if (fd == -1) {
+            return 0;
+        }
+        close(fd);
+    }
+    
+    // We have to still use the host namespace pid here for the kill() call
+    kill(pid, SIGQUIT);
+    
+    // Start with 20 ms sleep and increment delay each iteration
+    struct timespec ts = {0, 20000000};
+    int result;
+    do {
+        nanosleep(&ts, NULL);
+        result = check_socket(nspid);
+    } while (!result && (ts.tv_nsec += 20000000) < 300000000);
+
+    unlink(path);
+    return result;
+}
+
+// Connect to UNIX domain socket created by JVM for Dynamic Attach
+static int connect_socket(int pid) {
+    int fd = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (fd == -1) {
+        return -1;
+    }
+    
+    struct sockaddr_un addr;
+    addr.sun_family = AF_UNIX;
+    int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", get_temp_path(), pid);
+    if (bytes >= sizeof(addr.sun_path)) {
+        addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
+    }
+
+    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
+        close(fd);
+        return -1;
+    }
+    return fd;
+}
+
+// Send command with arguments to socket
+static int write_command(int fd, int argc, char** argv) {
+    // Protocol version
+    if (write(fd, "1", 2) <= 0) {
+        return 0;
+    }
+
+    int i;
+    for (i = 0; i < 4; i++) {
+        const char* arg = i < argc ? argv[i] : "";
+        if (write(fd, arg, strlen(arg) + 1) <= 0) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+// Mirror response from remote JVM to stdout
+static int read_response(int fd) {
+    char buf[8192];
+    ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
+    if (bytes <= 0) {
+        perror("Error reading response");
+        return 1;
+    }
+
+    // First line of response is the command result code
+    buf[bytes] = 0;
+    int result = atoi(buf);
+
+    do {
+        fwrite(buf, 1, bytes, stdout);
+        bytes = read(fd, buf, sizeof(buf));
+    } while (bytes > 0);
+
+    return result;
+}
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
+               "Copyright 2018 Andrei Pangin\n"
+               "\n"
+               "Usage: jattach <pid> <cmd> [args ...]\n");
+        return 1;
+    }
+    
+    int pid = atoi(argv[1]);
+    if (pid == 0) {
+        perror("Invalid pid provided");
+        return 1;
+    }
+
+    uid_t my_uid = geteuid();
+    gid_t my_gid = getegid();
+    uid_t target_uid = my_uid;
+    gid_t target_gid = my_gid;
+    int nspid = -1;
+    if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
+        fprintf(stderr, "Process %d not found\n", pid);
+        return 1;
+    }
+
+    if (nspid < 0) {
+        nspid = alt_lookup_nspid(pid);
+    }
+
+    // Make sure our /tmp and target /tmp is the same
+    if (!enter_mount_ns(pid)) {
+        printf("WARNING: couldn't enter target process mnt namespace\n");
+    }
+
+    // Dynamic attach is allowed only for the clients with the same euid/egid.
+    // If we are running under root, switch to the required euid/egid automatically.
+    if ((my_gid != target_gid && setegid(target_gid) != 0) ||
+        (my_uid != target_uid && seteuid(target_uid) != 0)) {
+        perror("Failed to change credentials to match the target process");
+        return 1;
+    }
+
+    // Make write() return EPIPE instead of silent process termination
+    signal(SIGPIPE, SIG_IGN);
+
+    if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
+        perror("Could not start attach mechanism");
+        return 1;
+    }
+
+    int fd = connect_socket(nspid);
+    if (fd == -1) {
+        perror("Could not connect to socket");
+        return 1;
+    }
+    
+    printf("Connected to remote JVM\n");
+    if (!write_command(fd, argc - 2, argv + 2)) {
+        perror("Error writing to socket");
+        close(fd);
+        return 1;
+    }
+
+    printf("Response code = ");
+    fflush(stdout);
+
+    int result = read_response(fd);
+    printf("\n");
+    close(fd);
+
+    return result;
+}
diff --git a/src/jattach_windows.c b/src/jattach_windows.c
new file mode 100644 (file)
index 0000000..8f8d25f
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2016 Andrei Pangin
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <Windows.h>
+
+typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName);
+typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName);
+typedef int (__stdcall *JVM_EnqueueOperation_t)(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename);
+
+typedef struct {
+    GetModuleHandle_t GetModuleHandleA;
+    GetProcAddress_t GetProcAddress;
+    char strJvm[32];
+    char strEnqueue[32];
+    char pipeName[MAX_PATH];
+    char args[4][MAX_PATH];
+} CallData;
+
+
+#pragma check_stack(off)
+
+// This code is executed in remote JVM process; be careful with memory it accesses
+DWORD WINAPI remote_thread_entry(LPVOID param) {
+    CallData* data = (CallData*)param;
+
+    HMODULE libJvm = data->GetModuleHandleA(data->strJvm);
+    if (libJvm != NULL) {
+        JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue);
+        if (JVM_EnqueueOperation != NULL) {
+            return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName);
+        }
+    }
+
+    return 0xffff;
+}
+
+#pragma check_stack
+
+
+// Allocate executable memory in remote process
+static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) {
+    SIZE_T codeSize = 1024;
+    LPVOID code = VirtualAllocEx(hProcess, NULL, codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
+    if (code != NULL) {
+        WriteProcessMemory(hProcess, code, remote_thread_entry, codeSize, NULL);
+    }
+    return (LPTHREAD_START_ROUTINE)code;
+}
+
+// Allocate memory for CallData in remote process
+static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** argv) {
+    CallData data;
+    data.GetModuleHandleA = GetModuleHandleA;
+    data.GetProcAddress = GetProcAddress;
+    strcpy(data.strJvm, "jvm");
+    strcpy(data.strEnqueue, "JVM_EnqueueOperation");
+    strcpy(data.pipeName, pipeName);
+
+    int i;
+    for (i = 0; i < 4; i++) {
+        strcpy(data.args[i], i < argc ? argv[i] : "");
+    }
+
+    LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE);
+    if (remoteData != NULL) {
+        WriteProcessMemory(hProcess, remoteData, &data, sizeof(data), NULL);
+    }
+    return remoteData;
+}
+
+static void print_error(const char* msg, DWORD code) {
+    printf("%s (error code = %d)\n", msg, code);
+}
+
+// If the process is owned by another user, request SeDebugPrivilege to open it.
+// Debug privileges are typically granted to Administrators.
+static int enable_debug_privileges() {
+    HANDLE hToken;
+    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
+        if (!ImpersonateSelf(SecurityImpersonation) ||
+            !OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
+            return 0;
+        }
+    }
+
+    LUID luid;
+    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
+        return 0;
+    }
+
+    TOKEN_PRIVILEGES tp;
+    tp.PrivilegeCount = 1;
+    tp.Privileges[0].Luid = luid;
+    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+    BOOL success = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
+    CloseHandle(hToken);
+    return success ? 1 : 0;
+}
+
+// The idea of Dynamic Attach on Windows is to inject a thread into remote JVM
+// that calls JVM_EnqueueOperation() function exported by HotSpot DLL
+static int inject_thread(int pid, char* pipeName, int argc, char** argv) {
+    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
+    if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
+        if (!enable_debug_privileges()) {
+            print_error("Not enough privileges", GetLastError());
+            return 0;
+        }
+        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
+    }
+    if (hProcess == NULL) {
+        print_error("Could not open process", GetLastError());
+        return 0;
+    }
+
+    LPTHREAD_START_ROUTINE code = allocate_code(hProcess);
+    LPVOID data = code != NULL ? allocate_data(hProcess, pipeName, argc, argv) : NULL;
+    if (data == NULL) {
+        print_error("Could not allocate memory in target process", GetLastError());
+        CloseHandle(hProcess);
+        return 0;
+    }
+
+    int success = 1;
+    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, code, data, 0, NULL);
+    if (hThread == NULL) {
+        print_error("Could not create remote thread", GetLastError());
+        success = 0;
+    } else {
+        printf("Connected to remote process\n");
+        WaitForSingleObject(hThread, INFINITE);
+        DWORD exitCode;
+        GetExitCodeThread(hThread, &exitCode);
+        if (exitCode != 0) {
+            print_error("Attach is not supported by the target process", exitCode);
+            success = 0;
+        }
+        CloseHandle(hThread);
+    }
+
+    VirtualFreeEx(hProcess, code, 0, MEM_RELEASE);
+    VirtualFreeEx(hProcess, data, 0, MEM_RELEASE);
+    CloseHandle(hProcess);
+
+    return success;
+}
+
+// JVM response is read from the pipe and mirrored to stdout
+static int read_response(HANDLE hPipe) {
+    ConnectNamedPipe(hPipe, NULL);
+
+    char buf[8192];
+    DWORD bytesRead;
+    if (!ReadFile(hPipe, buf, sizeof(buf) - 1, &bytesRead, NULL)) {
+        print_error("Error reading response", GetLastError());
+        return 1;
+    }
+
+    // First line of response is the command result code
+    buf[bytesRead] = 0;
+    int result = atoi(buf);
+
+    do {
+        fwrite(buf, 1, bytesRead, stdout);
+    } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL));
+
+    return result;
+}
+
+int main(int argc, char** argv) {
+    if (argc < 3) {
+        printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
+               "Copyright 2018 Andrei Pangin\n"
+               "\n"
+               "Usage: jattach <pid> <cmd> [args ...]\n");
+        return 1;
+    }
+
+    int pid = atoi(argv[1]);
+
+    char pipeName[MAX_PATH];
+    sprintf(pipeName, "\\\\.\\pipe\\javatool%d", GetTickCount());
+    HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+        1, 4096, 8192, NMPWAIT_USE_DEFAULT_WAIT, NULL);
+    if (hPipe == NULL) {
+        print_error("Could not create pipe", GetLastError());
+        return 1;
+    }
+
+    if (!inject_thread(pid, pipeName, argc - 2, argv + 2)) {
+        CloseHandle(hPipe);
+        return 1;
+    }
+
+    printf("Response code = ");
+    fflush(stdout);
+
+    int result = read_response(hPipe);
+    printf("\n");
+    CloseHandle(hPipe);
+
+    return result;
+}