]> git.sven.stormbind.net Git - sven/jattach.git/blob - src/posix/jattach_openj9.c
releasing package jattach version 2.2-1
[sven/jattach.git] / src / posix / jattach_openj9.c
1 /*
2  * Copyright 2021 Andrei Pangin
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/file.h>
21 #include <sys/ipc.h>
22 #include <sys/sem.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <netinet/in.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include "psutil.h"
32
33
34 #define MAX_NOTIF_FILES 256
35 static int notif_lock[MAX_NOTIF_FILES];
36
37
38 // Translate HotSpot command to OpenJ9 equivalent
39 static void translate_command(char* buf, size_t bufsize, int argc, char** argv) {
40     const char* cmd = argv[0];
41
42     if (strcmp(cmd, "load") == 0 && argc >= 2) {
43         if (argc > 2 && strcmp(argv[2], "true") == 0) {
44             snprintf(buf, bufsize, "ATTACH_LOADAGENTPATH(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
45         } else {
46             snprintf(buf, bufsize, "ATTACH_LOADAGENT(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
47         }
48
49     } else if (strcmp(cmd, "jcmd") == 0) {
50         snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : "");
51
52     } else if (strcmp(cmd, "threaddump") == 0) {
53         snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : "");
54
55     } else if (strcmp(cmd, "dumpheap") == 0) {
56         snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : "");
57
58     } else if (strcmp(cmd, "inspectheap") == 0) {
59         snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : "");
60
61     } else if (strcmp(cmd, "datadump") == 0) {
62         snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : "");
63
64     } else if (strcmp(cmd, "properties") == 0) {
65         strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES");
66
67     } else if (strcmp(cmd, "agentProperties") == 0) {
68         strcpy(buf, "ATTACH_GETAGENTPROPERTIES");
69
70     } else {
71         snprintf(buf, bufsize, "%s", cmd);
72     }
73
74     buf[bufsize - 1] = 0;
75 }
76
77 // Unescape a string and print it on stdout
78 static void print_unescaped(char* str) {
79     char* p = strchr(str, '\n');
80     if (p != NULL) {
81         *p = 0;
82     }
83
84     while ((p = strchr(str, '\\')) != NULL) {
85         switch (p[1]) {
86             case 0:
87                 break;
88             case 'f':
89                 *p = '\f';
90                 break;
91             case 'n':
92                 *p = '\n';
93                 break;
94             case 'r':
95                 *p = '\r';
96                 break;
97             case 't':
98                 *p = '\t';
99                 break;
100             default:
101                 *p = p[1];
102         }
103         fwrite(str, 1, p - str + 1, stdout);
104         str = p + 2;
105     }
106
107     fwrite(str, 1, strlen(str), stdout);
108     printf("\n");
109 }
110
111 // Send command with arguments to socket
112 static int write_command(int fd, const char* cmd) {
113     size_t len = strlen(cmd) + 1;
114     size_t off = 0;
115     while (off < len) {
116         ssize_t bytes = write(fd, cmd + off, len - off);
117         if (bytes <= 0) {
118             return -1;
119         }
120         off += bytes;
121     }
122     return 0;
123 }
124
125 // Mirror response from remote JVM to stdout
126 static int read_response(int fd, const char* cmd) {
127     size_t size = 8192;
128     char* buf = malloc(size);
129
130     size_t off = 0;
131     while (buf != NULL) {
132         ssize_t bytes = read(fd, buf + off, size - off);
133         if (bytes == 0) {
134             fprintf(stderr, "Unexpected EOF reading response\n");
135             return 1;
136         } else if (bytes < 0) {
137             perror("Error reading response");
138             return 1;
139         }
140
141         off += bytes;
142         if (buf[off - 1] == 0) {
143             break;
144         }
145
146         if (off >= size) {
147             buf = realloc(buf, size *= 2);
148         }
149     }
150
151     if (buf == NULL) {
152         fprintf(stderr, "Failed to allocate memory for response\n");
153         return 1;
154     }
155
156     int result = 0;
157
158     if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) {
159         if (strncmp(buf, "ATTACH_ACK", 10) != 0) {
160             // AgentOnLoad error code comes right after AgentInitializationException
161             result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1;
162         }
163     } else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0) {
164         char* p = strstr(buf, "openj9_diagnostics.string_result=");
165         if (p != NULL) {
166             // The result of a diagnostic command is encoded in Java Properties format
167             print_unescaped(p + 33);
168             free(buf);
169             return result;
170         }
171     }
172
173     buf[off - 1] = '\n';
174     fwrite(buf, 1, off, stdout);
175
176     free(buf);
177     return result;
178 }
179
180 static void detach(int fd) {
181     if (write_command(fd, "ATTACH_DETACHED") != 0) {
182         return;
183     }
184
185     char buf[256];
186     ssize_t bytes;
187     do {
188         bytes = read(fd, buf, sizeof(buf));
189     } while (bytes > 0 && buf[bytes - 1] != 0);
190 }
191
192 static void close_with_errno(int fd) {
193     int saved_errno = errno;
194     close(fd);
195     errno = saved_errno;
196 }
197
198 static int acquire_lock(const char* subdir, const char* filename) {
199     char path[MAX_PATH];
200     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename);
201
202     int lock_fd = open(path, O_WRONLY | O_CREAT, 0666);
203     if (lock_fd < 0) {
204         return -1;
205     }
206
207     if (flock(lock_fd, LOCK_EX) < 0) {
208         close_with_errno(lock_fd);
209         return -1;
210     }
211
212     return lock_fd;
213 }
214
215 static void release_lock(int lock_fd) {
216     flock(lock_fd, LOCK_UN);
217     close(lock_fd);
218 }
219
220 static int create_attach_socket(int* port) {
221     // Try IPv6 socket first, then fall back to IPv4
222     int s = socket(AF_INET6, SOCK_STREAM, 0);
223     if (s != -1) {
224         struct sockaddr_in6 addr = {AF_INET6, 0};
225         socklen_t addrlen = sizeof(addr);
226         if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
227                 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
228             *port = ntohs(addr.sin6_port);
229             return s;
230         }
231     } else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
232         struct sockaddr_in addr = {AF_INET, 0};
233         socklen_t addrlen = sizeof(addr);
234         if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
235                 && getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
236             *port = ntohs(addr.sin_port);
237             return s;
238         }
239     }
240
241     close_with_errno(s);
242     return -1;
243 }
244
245 static void close_attach_socket(int s, int pid) {
246     char path[MAX_PATH];
247     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
248     unlink(path);
249
250     close(s);
251 }
252
253 static unsigned long long random_key() {
254     unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL;
255
256     int fd = open("/dev/urandom", O_RDONLY);
257     if (fd >= 0) {
258         ssize_t r = read(fd, &key, sizeof(key));
259         (void)r;
260         close(fd);
261     }
262
263     return key;
264 }
265
266 static int write_reply_info(int pid, int port, unsigned long long key) {
267     char path[MAX_PATH];
268     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
269
270     int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
271     if (fd < 0) {
272         return -1;
273     }
274
275     int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port);
276     ssize_t r = write(fd, path, chars);
277     (void)r;
278     close(fd);
279
280     return 0;
281 }
282
283 static int notify_semaphore(int value, int notif_count) {
284     char path[MAX_PATH];
285     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
286
287     key_t sem_key = ftok(path, 0xa1);
288     int sem = semget(sem_key, 1, IPC_CREAT | 0666);
289     if (sem < 0) {
290         return -1;
291     }
292
293     struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
294     while (notif_count-- > 0) {
295         semop(sem, &op, 1);
296     }
297
298     return 0;
299 }
300
301 static int accept_client(int s, unsigned long long key) {
302     struct timeval tv = {5, 0};
303     setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
304
305     int client = accept(s, NULL, NULL);
306     if (client < 0) {
307         perror("JVM did not respond");
308         return -1;
309     }
310
311     char buf[35];
312     size_t off = 0;
313     while (off < sizeof(buf)) {
314         ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
315         if (bytes <= 0) {
316             fprintf(stderr, "The JVM connection was prematurely closed\n");
317             close(client);
318             return -1;
319         }
320         off += bytes;
321     }
322
323     char expected[35];
324     snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
325     if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
326         fprintf(stderr, "Unexpected JVM response\n");
327         close(client);
328         return -1;
329     }
330
331     // Reset the timeout, as the command execution may take arbitrary long time
332     struct timeval tv0 = {0, 0};
333     setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv0, sizeof(tv0));
334
335     return client;
336 }
337
338 static int lock_notification_files() {
339     int count = 0;
340
341     char path[MAX_PATH];
342     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
343
344     DIR* dir = opendir(path);
345     if (dir != NULL) {
346         struct dirent* entry;
347         while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
348             if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
349                 (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
350                 notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
351             }
352         }
353         closedir(dir);
354     }
355
356     return count;
357 }
358
359 static void unlock_notification_files(int count) {
360     int i;
361     for (i = 0; i < count; i++) {
362         if (notif_lock[i] >= 0) {
363             release_lock(notif_lock[i]);
364         }
365     }
366 }
367
368 int is_openj9_process(int pid) {
369     char path[MAX_PATH];
370     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
371
372     struct stat stats;
373     return stat(path, &stats) == 0;
374 }
375
376 int jattach_openj9(int pid, int nspid, int argc, char** argv) {
377     int attach_lock = acquire_lock("", "_attachlock");
378     if (attach_lock < 0) {
379         perror("Could not acquire attach lock");
380         return 1;
381     }
382
383     int notif_count = 0;
384     int port;
385     int s = create_attach_socket(&port);
386     if (s < 0) {
387         perror("Failed to listen to attach socket");
388         goto error;
389     }
390
391     unsigned long long key = random_key();
392     if (write_reply_info(nspid, port, key) != 0) {
393         perror("Could not write replyInfo");
394         goto error;
395     }
396
397     notif_count = lock_notification_files();
398     if (notify_semaphore(1, notif_count) != 0) {
399         perror("Could not notify semaphore");
400         goto error;
401     }
402
403     int fd = accept_client(s, key);
404     if (fd < 0) {
405         // The error message has been already printed
406         goto error;
407     }
408
409     close_attach_socket(s, nspid);
410     unlock_notification_files(notif_count);
411     notify_semaphore(-1, notif_count);
412     release_lock(attach_lock);
413
414     printf("Connected to remote JVM\n");
415
416     char cmd[8192];
417     translate_command(cmd, sizeof(cmd), argc, argv);
418
419     if (write_command(fd, cmd) != 0) {
420         perror("Error writing to socket");
421         close(fd);
422         return 1;
423     }
424
425     int result = read_response(fd, cmd);
426     if (result != 1) {
427         detach(fd);
428     }
429     close(fd);
430
431     return result;
432
433 error:
434     if (s >= 0) {
435         close_attach_socket(s, nspid);
436     }
437     if (notif_count > 0) {
438         unlock_notification_files(notif_count);
439         notify_semaphore(-1, notif_count);
440     }
441     release_lock(attach_lock);
442
443     return 1;
444 }