1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.tools.common;
27
28import java.net.URISyntaxException;
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.List;
32import java.util.stream.Collectors;
33
34import com.sun.tools.attach.VirtualMachine;
35import com.sun.tools.attach.VirtualMachineDescriptor;
36
37import sun.jvmstat.monitor.MonitorException;
38import sun.jvmstat.monitor.MonitoredHost;
39import sun.jvmstat.monitor.MonitoredVm;
40import sun.jvmstat.monitor.MonitoredVmUtil;
41import sun.jvmstat.monitor.VmIdentifier;
42
43/**
44 * Class for finding process matching a process argument,
45 * excluding tool it self and returning a list containing
46 * the process identifiers.
47 */
48public class ProcessArgumentMatcher {
49    private String matchClass;
50    private String singlePid;
51
52    public ProcessArgumentMatcher(String pidArg) {
53        if (pidArg == null || pidArg.isEmpty()) {
54            throw new IllegalArgumentException("Pid string is invalid");
55        }
56        if (pidArg.charAt(0) == '-') {
57            throw new IllegalArgumentException("Unrecognized " + pidArg);
58        }
59        try {
60            long pid = Long.parseLong(pidArg);
61            if (pid != 0) {
62                singlePid = String.valueOf(pid);
63            }
64        } catch (NumberFormatException nfe) {
65            matchClass = pidArg;
66        }
67    }
68
69    private static String getExcludeStringFrom(Class<?> excludeClass) {
70        if (excludeClass == null) {
71            return "";
72        }
73        Module m = excludeClass.getModule();
74        if (m.isNamed()) {
75            return m.getName() + "/" + excludeClass.getName();
76        }
77        return excludeClass.getName();
78    }
79
80    private static boolean check(VirtualMachineDescriptor vmd, String excludeClass, String partialMatch) {
81        String mainClass = null;
82        try {
83            VmIdentifier vmId = new VmIdentifier(vmd.id());
84            MonitoredHost monitoredHost = MonitoredHost.getMonitoredHost(vmId);
85            MonitoredVm monitoredVm = monitoredHost.getMonitoredVm(vmId, -1);
86            mainClass = MonitoredVmUtil.mainClass(monitoredVm, true);
87            monitoredHost.detach(monitoredVm);
88        } catch (NullPointerException npe) {
89            // There is a potential race, where a running java app is being
90            // queried, unfortunately the java app has shutdown after this
91            // method is started but before getMonitoredVM is called.
92            // If this is the case, then the /tmp/hsperfdata_xxx/pid file
93            // will have disappeared and we will get a NullPointerException.
94            // Handle this gracefully....
95            return false;
96        } catch (MonitorException | URISyntaxException e) {
97            return false;
98        }
99
100        if (excludeClass != null && mainClass.equals(excludeClass)) {
101            return false;
102        }
103
104        if (partialMatch != null && mainClass.indexOf(partialMatch) == -1) {
105            return false;
106        }
107
108        return true;
109    }
110
111    private static Collection<VirtualMachineDescriptor> getSingleVMD(String pid) {
112        Collection<VirtualMachineDescriptor> vids = new ArrayList<>();
113        List<VirtualMachineDescriptor> vmds = VirtualMachine.list();
114        for (VirtualMachineDescriptor vmd : vmds) {
115            if (check(vmd, null, null)) {
116                if (pid.equals(vmd.id())) {
117                    vids.add(vmd);
118                }
119            }
120        }
121        return vids;
122    }
123
124    private static Collection<VirtualMachineDescriptor> getVMDs(Class<?> excludeClass, String partialMatch) {
125        String excludeCls = getExcludeStringFrom(excludeClass);
126        Collection<VirtualMachineDescriptor> vids = new ArrayList<>();
127        List<VirtualMachineDescriptor> vmds = VirtualMachine.list();
128        for (VirtualMachineDescriptor vmd : vmds) {
129            if (check(vmd, excludeCls, partialMatch)) {
130                vids.add(vmd);
131            }
132        }
133        return vids;
134    }
135
136    public Collection<VirtualMachineDescriptor> getVirtualMachineDescriptors(Class<?> excludeClass) {
137        if (singlePid != null) {
138            return getSingleVMD(singlePid);
139        } else {
140            return getVMDs(excludeClass, matchClass);
141        }
142    }
143
144    public Collection<VirtualMachineDescriptor> getVirtualMachineDescriptors() {
145        return this.getVirtualMachineDescriptors(null);
146    }
147
148    public Collection<String> getVirtualMachinePids(Class<?> excludeClass) {
149        if (singlePid != null) {
150            // There is a bug in AttachProvider, when VM is debuggee-suspended it's not listed by the AttachProvider.
151            // If we are talking about a specific pid, just return it.
152            return List.of(singlePid);
153        } else {
154            return getVMDs(excludeClass, matchClass).stream().map(x -> {return x.id();}).collect(Collectors.toList());
155        }
156    }
157
158    public Collection<String> getVirtualMachinePids() {
159        return this.getVirtualMachinePids(null);
160    }
161}
162