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