]> git.sven.stormbind.net Git - sven/jattach.git/blob - src/posix/jattach_openj9.c
New upstream version 2.2
[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     write(fd, path, chars);
277     close(fd);
278
279     return 0;
280 }
281
282 static int notify_semaphore(int value, int notif_count) {
283     char path[MAX_PATH];
284     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
285
286     key_t sem_key = ftok(path, 0xa1);
287     int sem = semget(sem_key, 1, IPC_CREAT | 0666);
288     if (sem < 0) {
289         return -1;
290     }
291
292     struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
293     while (notif_count-- > 0) {
294         semop(sem, &op, 1);
295     }
296
297     return 0;
298 }
299
300 static int accept_client(int s, unsigned long long key) {
301     struct timeval tv = {5, 0};
302     setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
303
304     int client = accept(s, NULL, NULL);
305     if (client < 0) {
306         perror("JVM did not respond");
307         return -1;
308     }
309
310     char buf[35];
311     size_t off = 0;
312     while (off < sizeof(buf)) {
313         ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
314         if (bytes <= 0) {
315             fprintf(stderr, "The JVM connection was prematurely closed\n");
316             close(client);
317             return -1;
318         }
319         off += bytes;
320     }
321
322     char expected[35];
323     snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
324     if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
325         fprintf(stderr, "Unexpected JVM response\n");
326         close(client);
327         return -1;
328     }
329
330     return client;
331 }
332
333 static int lock_notification_files() {
334     int count = 0;
335
336     char path[MAX_PATH];
337     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
338
339     DIR* dir = opendir(path);
340     if (dir != NULL) {
341         struct dirent* entry;
342         while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
343             if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
344                 (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
345                 notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
346             }
347         }
348         closedir(dir);
349     }
350
351     return count;
352 }
353
354 static void unlock_notification_files(int count) {
355     int i;
356     for (i = 0; i < count; i++) {
357         if (notif_lock[i] >= 0) {
358             release_lock(notif_lock[i]);
359         }
360     }
361 }
362
363 int is_openj9_process(int pid) {
364     char path[MAX_PATH];
365     snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
366
367     struct stat stats;
368     return stat(path, &stats) == 0;
369 }
370
371 int jattach_openj9(int pid, int nspid, int argc, char** argv) {
372     int attach_lock = acquire_lock("", "_attachlock");
373     if (attach_lock < 0) {
374         perror("Could not acquire attach lock");
375         return 1;
376     }
377
378     int notif_count = 0;
379     int port;
380     int s = create_attach_socket(&port);
381     if (s < 0) {
382         perror("Failed to listen to attach socket");
383         goto error;
384     }
385
386     unsigned long long key = random_key();
387     if (write_reply_info(nspid, port, key) != 0) {
388         perror("Could not write replyInfo");
389         goto error;
390     }
391
392     notif_count = lock_notification_files();
393     if (notify_semaphore(1, notif_count) != 0) {
394         perror("Could not notify semaphore");
395         goto error;
396     }
397
398     int fd = accept_client(s, key);
399     if (fd < 0) {
400         // The error message has been already printed
401         goto error;
402     }
403
404     close_attach_socket(s, nspid);
405     unlock_notification_files(notif_count);
406     notify_semaphore(-1, notif_count);
407     release_lock(attach_lock);
408
409     printf("Connected to remote JVM\n");
410
411     char cmd[8192];
412     translate_command(cmd, sizeof(cmd), argc, argv);
413
414     if (write_command(fd, cmd) != 0) {
415         perror("Error writing to socket");
416         close(fd);
417         return 1;
418     }
419
420     int result = read_response(fd, cmd);
421     if (result != 1) {
422         detach(fd);
423     }
424     close(fd);
425
426     return result;
427
428 error:
429     if (s >= 0) {
430         close_attach_socket(s, nspid);
431     }
432     if (notif_count > 0) {
433         unlock_notification_files(notif_count);
434         notify_semaphore(-1, notif_count);
435     }
436     release_lock(attach_lock);
437
438     return 1;
439 }