OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
93 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
93 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
16
import static org.openconcerto.utils.DesktopEnvironment.cmdSubstitution;
180 ilm 17
 
93 ilm 18
import org.openconcerto.utils.cc.ITransformer;
19
 
20
import java.io.BufferedReader;
182 ilm 21
import java.io.ByteArrayOutputStream;
93 ilm 22
import java.io.File;
23
import java.io.IOException;
24
import java.io.InputStreamReader;
132 ilm 25
import java.math.BigDecimal;
93 ilm 26
import java.net.InetAddress;
27
import java.net.SocketException;
180 ilm 28
import java.nio.file.Files;
182 ilm 29
import java.nio.file.Path;
180 ilm 30
import java.nio.file.Paths;
93 ilm 31
import java.util.ArrayList;
32
import java.util.List;
33
import java.util.logging.Level;
132 ilm 34
import java.util.regex.Matcher;
35
import java.util.regex.Pattern;
93 ilm 36
 
37
/**
38
 * To abstract differences between platform for non java tasks.
39
 *
40
 * @author Sylvain
41
 * @see DesktopEnvironment
42
 */
43
public abstract class Platform {
44
 
182 ilm 45
    public static final String PROCESS_ALLOW_AMBIGUOUS_COMMANDS = "jdk.lang.Process.allowAmbiguousCommands";
46
 
93 ilm 47
    private static final int PING_TIMEOUT = 250;
48
 
49
    public static final Platform getInstance() {
50
        final OSFamily os = OSFamily.getInstance();
51
        if (os == OSFamily.Windows) {
52
            return CYGWIN;
180 ilm 53
        } else if (os == OSFamily.FreeBSD) {
93 ilm 54
            return FREEBSD;
180 ilm 55
        } else if (os == OSFamily.Mac) {
56
            return MACOS;
93 ilm 57
        } else {
58
            return LINUX;
59
        }
60
    }
61
 
182 ilm 62
    public static class CannotPassArgumentException extends RuntimeException {
63
        private final String arg;
64
 
65
        private CannotPassArgumentException(String arg, String message) {
66
            super("Cannot pass " + arg + message);
67
            this.arg = arg;
68
        }
69
 
70
        public final String getArg() {
71
            return this.arg;
72
        }
73
    }
74
 
93 ilm 75
    public abstract boolean supportsPID();
76
 
77
    public abstract boolean isRunning(final int pid) throws IOException;
78
 
79
    public abstract String getPath(final File f);
80
 
182 ilm 81
    public String getPath(final Path p) {
82
        return this.getPath(p.toFile());
83
    }
84
 
85
    public String getProcessArg(final String arg) {
86
        return arg;
87
    }
88
 
93 ilm 89
    public final String getPID() throws IOException {
180 ilm 90
        // TODO remove reflection and getPreJava9PID() once on java 11
91
        try {
92
            final Class<?> phClass = Class.forName("java.lang.ProcessHandle");
93
            final Object ph = phClass.getMethod("current").invoke(null);
94
            return ((Number) phClass.getMethod("pid").invoke(ph)).toString();
95
        } catch (ClassNotFoundException e) {
96
            // fall back
97
        } catch (Exception e) {
98
            throw new IOException("Couldn't get PID", e);
99
        }
100
        return getPreJava9PID();
101
    }
102
 
103
    protected String getPreJava9PID() throws IOException {
93 ilm 104
        final Process p = this.eval("echo -n $PPID");
180 ilm 105
        try (final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
93 ilm 106
            return reader.readLine();
107
        }
108
    }
109
 
110
    public final String tail(final File f, final int n) throws IOException {
111
        final Process p = Runtime.getRuntime().exec(new String[] { "tail", "-n" + n, this.getPath(f) });
112
        return cmdSubstitution(p);
113
    }
114
 
115
    protected abstract String getBash();
116
 
117
    /**
118
     * Create a symbolic link from f2 to f1. NOTE: the path from f2 to f1 is made relative (eg
119
     * lastLog -> ./allLogs/tuesdayLog).
120
     *
121
     * @param f1 the destination of the link, eg "/dir/allLogs/tuesdayLog".
122
     * @param f2 the name of the link, eg "/dir/lastLog".
123
     * @throws IOException if an error occurs.
124
     */
125
    public final void ln_s(File f1, File f2) throws IOException {
126
        FileUtils.ln(f1, f2);
127
    }
128
 
129
    public boolean isSymLink(File f) throws IOException {
130
        return exitStatus(Runtime.getRuntime().exec(new String[] { "test", "-L", f.getAbsolutePath() })) == 0;
131
    }
132
 
133
    // see cygwin
134
    public File getNativeSymlinkFile(File dir) {
135
        return dir;
136
    }
137
 
138
    public abstract String readLink(File f) throws IOException;
139
 
140
    public final boolean exists(File f) throws IOException {
141
        return exitStatus(Runtime.getRuntime().exec(new String[] { "test", "-e", f.getAbsolutePath() })) == 0;
142
    }
143
 
144
    public final void append(File f1, File f2) throws IOException {
145
        final String c = "cat '" + f1.getAbsolutePath() + "' >> '" + f2.getAbsolutePath() + "'";
180 ilm 146
        this.waitForSuccess(this.eval(c), "append");
93 ilm 147
    }
148
 
149
    public Process cp_l(File src, File dest) throws IOException {
150
        return Runtime.getRuntime().exec(new String[] { "cp", "-prl", src.getAbsolutePath(), dest.getAbsolutePath() });
151
    }
152
 
153
    public final boolean ping(InetAddress host) throws IOException {
154
        return this.ping(host, PING_TIMEOUT);
155
    }
156
 
157
    /**
158
     * Test whether that address is reachable.
159
     *
160
     * @param host the host to reach.
161
     * @param timeout the time, in milliseconds, before the call aborts.
162
     * @return <code>true</code> if the address is reachable.
163
     * @throws IOException if a network error occurs.
164
     */
165
    public abstract boolean ping(InetAddress host, final int timeout) throws IOException;
166
 
167
    public final PingBuilder createPingBuilder() {
168
        return new PingBuilder(this);
169
    }
170
 
132 ilm 171
    protected abstract PingResult ping(InetAddress host, final PingBuilder pingBuilder, final int routingTableIndex) throws IOException;
93 ilm 172
 
132 ilm 173
    protected abstract BigDecimal parsePingAverageRT(String statsLine);
174
 
175
    protected final PingResult ping(final String command, final int totalCount, int requiredCount) throws IOException {
93 ilm 176
        if (requiredCount <= 0)
177
            requiredCount = totalCount;
180 ilm 178
        // Keep errors out of cmdSubstitution() (e.g. "ping: sendto: Message too long" when
179
        // setDontFragment(true))
180
        final Process proc = evalPB(command).redirectErrorStream(false).start();
182 ilm 181
 
182
        // some programs won't write anything until they read everything
183
        proc.getOutputStream().close();
184
        final ByteArrayOutputStream out = new ByteArrayOutputStream(5 * 1024);
185
        final ByteArrayOutputStream err = new ByteArrayOutputStream(2 * 1024);
186
        try (final ProcessStreams streams = new ProcessStreams(proc)) {
187
            streams.start(out, err);
188
            streams.awaitTermination();
189
        } catch (Exception e) {
190
            throw new IllegalStateException("Couldn't capture output of ping", e);
191
        }
192
 
193
        final String output = out.toString();
180 ilm 194
        try {
195
            this.waitForSuccess(proc, "ping");
196
            final List<String> countAndLastLine = StringUtils.splitIntoLines(output);
197
            if (countAndLastLine.size() != 2)
198
                throw new IllegalStateException("Not 2 lines in " + countAndLastLine);
199
            final int replied = Integer.parseInt(countAndLastLine.get(0));
200
            assert replied <= totalCount;
201
            final BigDecimal averageRTT = replied == 0 ? null : parsePingAverageRT(countAndLastLine.get(1).trim());
202
            return new PingResult(totalCount, replied, requiredCount, averageRTT);
203
        } catch (Exception e) {
182 ilm 204
            throw new IllegalStateException("Couldn't use output :<<<\n" + output + "\n<<<\nerr:<<<\n" + err.toString() + "\n<<<", e);
180 ilm 205
        }
93 ilm 206
    }
207
 
208
    /**
209
     * Eval the passed string with bash.
210
     *
211
     * @param s a bash script.
212
     * @return the created process.
213
     * @throws IOException If an I/O error occurs.
214
     */
215
    public final Process eval(String s) throws IOException {
180 ilm 216
        return evalPB(s).start();
93 ilm 217
    }
218
 
180 ilm 219
    public final ProcessBuilder evalPB(String s) throws IOException {
220
        return new ProcessBuilder(this.getBash(), "-c", s);
221
    }
222
 
93 ilm 223
    public final int exitStatus(Process p) {
180 ilm 224
        return this.exitStatus(p, null);
225
    }
226
 
227
    public final int exitStatus(Process p, final String name) {
93 ilm 228
        try {
229
            return p.waitFor();
230
        } catch (InterruptedException e) {
180 ilm 231
            throw new RTInterruptedException("Interrupted while waiting for" + (name == null ? "" : " '" + name + "'") + " process", e);
93 ilm 232
        }
233
    }
234
 
180 ilm 235
    public final void waitForSuccess(final Process p, final String name) {
236
        final int exitStatus = exitStatus(p, name);
237
        if (exitStatus != 0)
238
            throw new IllegalStateException(name + " unsuccessful : " + exitStatus);
239
    }
240
 
93 ilm 241
    public abstract boolean isAdmin() throws IOException;
242
 
243
    private static abstract class UnixPlatform extends Platform {
244
 
245
        @Override
246
        public boolean supportsPID() {
247
            return true;
248
        }
249
 
180 ilm 250
        @Override
251
        public final String getPreJava9PID() throws IOException {
252
            final String symlink = getSelfProcessSymlink();
253
            if (symlink == null)
254
                return super.getPreJava9PID();
255
 
256
            // readSymbolicLink() seems to faster than getCanonicalFile() or toRealPath().
257
            // Another way is using reflection for
258
            // ManagementFactory.getRuntimeMXBean().jvm.getProcessId()
259
            return Files.readSymbolicLink(Paths.get(symlink)).getFileName().toString();
260
        }
261
 
262
        protected abstract String getSelfProcessSymlink();
263
 
93 ilm 264
        public final boolean isRunning(final int pid) throws IOException {
265
            // --pid only works on Linux, -p also on Nexenta
266
            final Process p = Runtime.getRuntime().exec(new String[] { "ps", "-p", String.valueOf(pid) });
267
            return this.exitStatus(p) == 0;
268
        }
269
 
182 ilm 270
        @Override
93 ilm 271
        public String getPath(final File f) {
272
            return f.getPath();
273
        }
274
 
275
        @Override
182 ilm 276
        public String getPath(final Path f) {
277
            return f.toString();
278
        }
279
 
280
        @Override
93 ilm 281
        protected String getBash() {
282
            return "bash";
283
        }
284
 
285
        @Override
286
        public String readLink(File f) throws IOException {
287
            // --no-newline long form not supported on FreeBSD
288
            final Process p = Runtime.getRuntime().exec(new String[] { "readlink", "-n", f.getAbsolutePath() });
289
            return cmdSubstitution(p);
290
        }
291
 
292
        @Override
293
        public boolean ping(InetAddress host, final int timeout) throws IOException {
294
            try {
295
                return host.isReachable(timeout);
296
            } catch (SocketException e) {
297
                // On FreeBSD at least, if the destination is blocked by the firewall :
298
                // java.net.SocketException: Operation not permitted
299
                // at java.net.Inet4AddressImpl.isReachable0(Native Method)
300
                // at java.net.Inet4AddressImpl.isReachable(Inet4AddressImpl.java:70)
301
                Log.get().log(Level.FINER, "Swallow exception", e);
302
                return false;
303
            }
304
        }
305
 
132 ilm 306
        // Linux : rtt min/avg/max/mdev = 12.552/13.399/14.247/0.855 ms
307
        // FreeBSD : round-trip min/avg/max/stddev = 0.301/0.371/0.442/0.071 ms
308
        private static final Pattern PING_STATS_PATTERN = Pattern
309
                .compile("^\\p{Blank}*(?:rtt|round-trip)\\p{Blank}+min/avg/max/(?:mdev|stddev)\\p{Blank}+=\\p{Blank}+([0-9\\.]+)/([0-9\\.]+)/([0-9\\.]+)/([0-9\\.]+)\\p{Blank}+ms$");
310
 
93 ilm 311
        @Override
132 ilm 312
        protected BigDecimal parsePingAverageRT(String statsLine) {
313
            final Matcher m = PING_STATS_PATTERN.matcher(statsLine);
314
            if (!m.matches())
180 ilm 315
                throw new IllegalArgumentException("Not matching " + PING_STATS_PATTERN + " :\n" + statsLine);
132 ilm 316
            return new BigDecimal(m.group(2));
317
        }
318
 
319
        @Override
93 ilm 320
        public boolean isAdmin() throws IOException {
321
            // root is uid 0
322
            return cmdSubstitution(this.eval("id -u")).trim().equals("0");
323
        }
324
    }
325
 
326
    private static final Platform LINUX = new UnixPlatform() {
180 ilm 327
 
93 ilm 328
        @Override
180 ilm 329
        protected String getSelfProcessSymlink() {
330
            return "/proc/self";
331
        }
332
 
333
        @Override
132 ilm 334
        public PingResult ping(final InetAddress host, final PingBuilder pingBuilder, final int routingTableIndex) throws IOException {
93 ilm 335
            if (routingTableIndex > 0)
336
                throw new UnsupportedOperationException("On Linux, choosing a different routing table requires changing the system policy");
337
            final List<String> command = new ArrayList<String>(16);
338
            command.add("ping");
339
            final int totalCount = pingBuilder.getTotalCount();
340
            command.add("-c");
341
            command.add(String.valueOf(totalCount));
342
 
343
            if (pingBuilder.getWaitTime() > 0) {
344
                command.add("-W");
345
                final int timeInSeconds = pingBuilder.getWaitTime() / 1000;
346
                command.add(String.valueOf(Math.max(timeInSeconds, 1)));
347
            }
348
 
349
            command.add("-M");
350
            command.add(pingBuilder.isDontFragment() ? "do" : "dont");
351
 
352
            if (pingBuilder.getLength() > 0) {
353
                command.add("-s");
354
                command.add(String.valueOf(pingBuilder.getLength()));
355
            }
356
            if (pingBuilder.getTTL() > 0) {
357
                command.add("-t");
358
                command.add(String.valueOf(pingBuilder.getTTL()));
359
            }
132 ilm 360
            if (pingBuilder.getSourceAddress() != null) {
361
                command.add("-I");
362
                command.add(pingBuilder.getSourceAddress());
363
            }
93 ilm 364
 
365
            command.add(host.getHostAddress());
366
 
132 ilm 367
            return ping("out=$(" + CollectionUtils.join(command, " ") + ") ; grep -c ttl= <<< \"$out\" ; tail -n1 <<< \"$out\"", totalCount, pingBuilder.getRequiredReplies());
93 ilm 368
        }
369
    };
370
 
371
    private static final Platform FREEBSD = new UnixPlatform() {
180 ilm 372
 
93 ilm 373
        @Override
180 ilm 374
        protected String getSelfProcessSymlink() {
375
            return "/proc/curproc";
376
        }
377
 
378
        @Override
132 ilm 379
        public PingResult ping(final InetAddress host, final PingBuilder pingBuilder, final int routingTableIndex) throws IOException {
93 ilm 380
            final List<String> command = new ArrayList<String>(16);
381
            command.add("setfib");
382
            command.add(String.valueOf(routingTableIndex));
383
            command.add("ping");
384
            final int totalCount = pingBuilder.getTotalCount();
385
            command.add("-c");
386
            command.add(String.valueOf(totalCount));
387
 
388
            if (pingBuilder.getWaitTime() > 0) {
389
                command.add("-W");
390
                command.add(String.valueOf(pingBuilder.getWaitTime()));
391
            }
392
 
393
            if (pingBuilder.isDontFragment()) {
394
                command.add("-D");
395
            }
396
            if (pingBuilder.getLength() > 0) {
397
                command.add("-s");
398
                command.add(String.valueOf(pingBuilder.getLength()));
399
            }
400
            if (pingBuilder.getTTL() > 0) {
401
                command.add("-m");
402
                command.add(String.valueOf(pingBuilder.getTTL()));
403
            }
132 ilm 404
            if (pingBuilder.getSourceAddress() != null) {
405
                command.add("-S");
406
                command.add(pingBuilder.getSourceAddress());
407
            }
93 ilm 408
 
409
            command.add(host.getHostAddress());
410
 
132 ilm 411
            return ping("out=$(" + CollectionUtils.join(command, " ") + ") ; grep -c ttl= <<< \"$out\" ; tail -n1 <<< \"$out\"", totalCount, pingBuilder.getRequiredReplies());
93 ilm 412
        }
413
    };
414
 
180 ilm 415
    private static final Platform MACOS = new UnixPlatform() {
416
        @Override
417
        protected String getSelfProcessSymlink() {
418
            return null;
419
        }
420
 
421
        @Override
422
        protected PingResult ping(InetAddress host, PingBuilder pingBuilder, int routingTableIndex) throws IOException {
423
            return FREEBSD.ping(host, pingBuilder, routingTableIndex);
424
        }
425
    };
426
 
182 ilm 427
    // return 171 for "1.8.0_171"
428
    // return 11 for "11.0.11"
429
    public static final int getUpdateVersion(final char sep) {
430
        final String vers = System.getProperty("java.version");
431
        final int lastIndexOf = vers.lastIndexOf(sep);
432
        // e.g. "13"
433
        if (lastIndexOf < 0)
434
            return 0;
435
        return Integer.parseInt(vers.substring(lastIndexOf + 1));
436
    }
93 ilm 437
 
182 ilm 438
    public static abstract class WindowsPlatform extends Platform {
439
        // on Windows program themselves are required to parse the command line, thus a lot of them
440
        // do it differently, see "How Command Line Parameters Are Parsed"
441
        // https://daviddeley.com/autohotkey/parameters/parameters.htm
442
 
443
        static private final Pattern quotePatrn = Pattern.compile("([\\\\]*)\"");
444
        static private final Pattern endSlashPatrn = Pattern.compile("([\\\\]+)\\z");
445
 
446
        static private boolean needsQuoting(String s) {
447
            final int len = s.length();
448
            if (len == 0) // empty string have to be quoted
449
                return true;
450
            for (int i = 0; i < len; i++) {
451
                switch (s.charAt(i)) {
452
                case ' ':
453
                case '\t':
454
                case '"':
455
                    return true;
456
                }
457
            }
458
            return false;
459
        }
460
 
461
        // see http://bugs.sun.com/view_bug.do?bug_id=6468220
462
        // e.g. find.exe, choice.exe
463
        public String quoteParamForMsftC(String s) {
464
            if (!needsQuoting(s))
465
                return s;
466
            if (s.length() > 0) {
467
                // replace '(\*)"' by '$1$1\"', e.g. '\quote " \"' by '\quote \" \\\"'
468
                // $1 needed so that the backslash we add isn't escaped itself by a preceding
469
                // backslash
470
                s = quotePatrn.matcher(s).replaceAll("$1$1\\\\\"");
471
                // replace '(\*)\z' by '$1$1', e.g. 'foo\' by 'foo\\'
472
                // needed to not escape closing quote
473
                s = endSlashPatrn.matcher(s).replaceAll("$1$1");
474
            }
475
            return '"' + s + '"';
476
        }
477
 
93 ilm 478
        @Override
182 ilm 479
        public String getProcessArg(String arg) {
480
            return this.getProcessArg(arg, false);
481
        }
482
 
483
        public final String getScriptProcessArg(String arg) {
484
            return this.getProcessArg(arg, true);
485
        }
486
 
487
        private String getProcessArg(String arg, final boolean script) {
488
            // Perhaps should have one method for .exe and one for .cmd/.bat (ProcessImpl checks
489
            // with isShellFile() and isExe()).
490
 
491
            if (script && arg.indexOf('"') >= 0)
492
                throw new CannotPassArgumentException(arg, ", it contains a double quote which is always removed by wscript!SplitCommandLine()");
493
 
494
            /*
495
             * If has VERIFICATION_WIN32_SAFE && !allowAmbiguousCommands, then ProcessImpl behaves
496
             * almost correctly : the argument we pass arrives as-is to the process (so the caller
497
             * shouldn't quote, ProcessImpl will fail with "Malformed argument has embedded quote"
498
             * if a character was escaped). Otherwise ProcessImpl does nothing if arg begins and
499
             * ends with double quotes (it's the caller responsibility to correctly quote).
500
             */
501
            final boolean doubleQuote;
502
            // 1.8 or 11
503
            final String specVers = System.getProperty("java.specification.version");
504
            // 8 or 11
505
            final int jreFamilyVersion = Integer.parseInt(specVers.startsWith("1.") ? specVers.substring(2) : specVers);
506
            final String javaVendor = System.getProperty("java.vendor");
507
            // VERIFICATION_WIN32_SAFE thanks to https://nvd.nist.gov/vuln/detail/CVE-2019-2958
508
            // For Oracle and OpenJDK :
509
            // https://www.oracle.com/java/technologies/javase/8u231-relnotes.html#JDK-8221858
510
            // https://github.com/openjdk/jdk/commit/5a98b8cfb0cd4c5ce84746e9fa5e42a86d1b2d24#diff-71e1dde85b2937bbf08d0bfd9e8a2e3553c578a3f58907ba6f4f30ec350db184
511
            // For AdoptOpenJDK :
512
            // https://github.com/AdoptOpenJDK/openjdk-jdk11u/commit/0074850a15405a676a492d09e4955d4053966bbc#diff-71e1dde85b2937bbf08d0bfd9e8a2e3553c578a3f58907ba6f4f30ec350db184
513
            if (jreFamilyVersion >= 14 || (javaVendor.equals("AdoptOpenJDK") && (jreFamilyVersion == 11 && getUpdateVersion('.') >= 5 || jreFamilyVersion == 13 && getUpdateVersion('.') >= 1))
514
                    || (jreFamilyVersion == 8 && getUpdateVersion('_') >= 231)) {
515
                // copied from ProcessImpl
516
                final SecurityManager security = System.getSecurityManager();
517
                final String value = System.getProperty(PROCESS_ALLOW_AMBIGUOUS_COMMANDS, (security == null ? "true" : "false"));
518
                final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value);
519
                if ((script || allowAmbiguousCommands) && arg.endsWith("\\")) {
520
                    // with script, we always do our own quoting since ProcessBuilder will double
521
                    // back slashes.
522
                    if (script || needsQuoting(arg)) {
523
                        throw new CannotPassArgumentException(arg,
524
                                ", if we'd double quote, then the string would end with 2 back slashes and 1 double quote, but ProcessImpl.unQuote() : 'not properly quoted, treat as unquoted'. So it would get quoted a second time. Try setting '"
525
                                        + PROCESS_ALLOW_AMBIGUOUS_COMMANDS + "' to 'false'.");
526
                    } else {
527
                        doubleQuote = false;
528
                    }
529
                } else if (!allowAmbiguousCommands && arg.length() >= 2 && arg.charAt(0) == '"' && arg.charAt(arg.length() - 1) == '"') {
530
                    throw new CannotPassArgumentException(arg,
531
                            "\nif we pass it as-is then ProcessImpl will consider the string already quoted and needsEscaping() will return false, thus the quotes will be removed by CommandLineToArgvW()"
532
                                    + "\nif we quote it then ProcessImpl.needsEscaping() will throw 'Malformed argument has embedded quote'");
533
                } else {
534
                    doubleQuote = allowAmbiguousCommands;
535
                }
536
            } else {
537
                doubleQuote = true;
538
            }
539
 
540
            if (script)
541
                return '"' + arg + '"';
542
            else
543
                return doubleQuote ? quoteParamForMsftC(arg) : arg;
544
        }
545
    }
546
 
547
    private static final class CygwinPlatform extends WindowsPlatform {
548
 
549
        @Override
93 ilm 550
        public boolean supportsPID() {
551
            return false;
552
        }
553
 
554
        @Override
555
        public boolean isRunning(int pid) throws IOException {
556
            // PID TTY STIME COMMAND
557
            // 864 ? 09:27:57 \??\C:\WINDOWS\system32\winlogon.exe
558
            // so if we count lines, we should get 2
559
            final Process p = this.eval("export PATH=$PATH:/usr/bin ; test $(ps -sW -p " + pid + " | wc -l) -eq 2 ");
560
            return this.exitStatus(p) == 0;
561
        }
562
 
563
        @Override
564
        protected String getBash() {
565
            // We used to specify the full path here, but this needed to be configurable (e.g.
566
            // cygwin 32 or 64 bit), plus all other programs are required to be on the PATH
567
            return "bash.exe";
568
        }
569
 
570
        @Override
571
        public String readLink(File f) throws IOException {
572
            // windows format to be able to use File
573
            final Process p = Runtime.getRuntime().exec(new String[] { "readshortcut.exe", "-w", f.getAbsolutePath() });
574
            return cmdSubstitution(p).trim();
575
        }
576
 
577
        @Override
578
        public String getPath(File f) {
579
            return toCygwinPath(f);
580
        }
581
 
582
        @Override
583
        public boolean ping(InetAddress host, final int timeout) throws IOException {
584
            // windows implem of isReachable() is buggy
585
            // see http://bordet.blogspot.com/2006/07/icmp-and-inetaddressisreachable.html
180 ilm 586
            final int exit = this.exitStatus(Runtime.getRuntime().exec("ping -n 1 -w " + timeout + " " + host.getHostAddress()), "ping");
587
            return exit == 0;
93 ilm 588
        }
589
 
590
        @Override
132 ilm 591
        public PingResult ping(final InetAddress host, final PingBuilder pingBuilder, final int routingTableIndex) throws IOException {
93 ilm 592
            if (routingTableIndex > 0)
593
                throw new UnsupportedOperationException("Only one routing table on Windows");
594
            final List<String> command = new ArrayList<String>(16);
595
            command.add("ping");
596
            final int totalCount = pingBuilder.getTotalCount();
597
            command.add("-n");
598
            command.add(String.valueOf(totalCount));
599
 
600
            if (pingBuilder.getWaitTime() > 0) {
601
                command.add("-w");
602
                command.add(String.valueOf(pingBuilder.getWaitTime()));
603
            }
604
 
605
            if (pingBuilder.isDontFragment()) {
606
                command.add("-f");
607
            }
608
            if (pingBuilder.getLength() > 0) {
609
                command.add("-l");
610
                command.add(String.valueOf(pingBuilder.getLength()));
611
            }
612
            if (pingBuilder.getTTL() > 0) {
613
                command.add("-i");
614
                command.add(String.valueOf(pingBuilder.getTTL()));
615
            }
132 ilm 616
            if (pingBuilder.getSourceAddress() != null) {
617
                command.add("-S");
618
                command.add(pingBuilder.getSourceAddress());
619
            }
93 ilm 620
 
621
            command.add(host.getHostAddress());
622
 
132 ilm 623
            // can't use local variable : never could work out newlines problem
624
            // see https://cygwin.com/ml/cygwin-announce/2011-02/msg00027.html
625
            return ping("tmpF=$(mktemp) && " + CollectionUtils.join(command, " ") + " > $tmpF ; grep -c \"TTL=\" < $tmpF ; tail -n1 $tmpF; rm \"$tmpF\"", totalCount, pingBuilder.getRequiredReplies());
93 ilm 626
        }
627
 
132 ilm 628
        // Minimum = 35ms, Maximum = 36ms, Moyenne = 35ms
629
        private static final Pattern PING_STATS_PATTERN = Pattern.compile("^\\p{Blank}*\\p{Alpha}+ = ([0-9\\.]+)ms, \\p{Alpha}+ = ([0-9\\.]+)ms, \\p{Alpha}+ = ([0-9\\.]+)ms$");
630
 
93 ilm 631
        @Override
132 ilm 632
        protected BigDecimal parsePingAverageRT(String statsLine) {
633
            final Matcher m = PING_STATS_PATTERN.matcher(statsLine);
634
            if (!m.matches())
635
                throw new IllegalArgumentException("Not matching " + PING_STATS_PATTERN + " : " + statsLine);
636
            return new BigDecimal(m.group(3));
637
        }
638
 
639
        @Override
93 ilm 640
        public boolean isAdmin() throws IOException {
132 ilm 641
            // SID administrators S-1-5-32-544 or S-1-5-114
642
            return this.exitStatus(this.eval("id -G | egrep '\\b(544|114)\\b'")) == 0;
93 ilm 643
        }
644
 
645
        @Override
646
        public boolean isSymLink(File f) throws IOException {
647
            // links created with "ln" can loose their "test -L" status over a copy (eg between two
648
            // Eclipse workspaces), so check with filename
649
            return getNativeSymlinkFile(f).exists() || super.isSymLink(f);
650
        }
651
 
652
        // when cygwin does "ln -s f link" it actually creates "link.lnk"
653
        @Override
654
        public File getNativeSymlinkFile(File dir) {
655
            return FileUtils.addSuffix(dir, ".lnk");
656
        }
657
    };
658
 
132 ilm 659
    private static final Platform CYGWIN = new CygwinPlatform();
660
 
93 ilm 661
    public static String toCygwinPath(File dir) {
662
        final List<File> ancestors = getAncestors(dir);
663
 
664
        final String root = ancestors.get(0).getPath();
665
        final List<File> rest = ancestors.subList(1, ancestors.size());
666
        return "/cygdrive/" + root.charAt(0) + "/" + CollectionUtils.join(rest, "/", new ITransformer<File, String>() {
667
            @Override
668
            public String transformChecked(File f) {
669
                return f.getName();
670
            }
671
        });
672
    }
673
 
674
    public static List<File> getAncestors(File f) {
675
        final File abs = f.getAbsoluteFile();
676
        File current = abs;
677
        final List<File> res = new ArrayList<File>();
678
 
679
        while (current != null) {
680
            res.add(0, current);
681
            current = current.getParentFile();
682
        }
683
        return res;
684
    }
685
 
132 ilm 686
    public static final class PingResult {
687
 
688
        private final int wantedCount, repliesCount, requiredReplies;
689
        private final BigDecimal averageRTT;
690
 
691
        private PingResult(final int wantedCount, final int repliesCount, final int requiredReplies, final BigDecimal averageRTT) {
692
            this.wantedCount = wantedCount;
693
            this.repliesCount = repliesCount;
694
            this.requiredReplies = requiredReplies;
695
            this.averageRTT = averageRTT;
696
        }
697
 
698
        public final int getRepliesCount() {
699
            return this.repliesCount;
700
        }
701
 
702
        public final int getRequiredReplies() {
703
            return this.requiredReplies;
704
        }
705
 
706
        public final boolean hasRequiredReplies() {
707
            return this.hasEnoughReplies(this.getRequiredReplies());
708
        }
709
 
710
        public final boolean hasEnoughReplies(final int min) {
711
            return this.getRepliesCount() >= min;
712
        }
713
 
714
        public final BigDecimal getAverageRTT() {
715
            return this.averageRTT;
716
        }
717
 
718
        @Override
719
        public String toString() {
720
            return this.getClass().getSimpleName() + " " + this.getRepliesCount() + "/" + this.wantedCount + " reply(ies), average RTT : " + this.getAverageRTT() + " ms";
721
        }
722
    }
723
 
93 ilm 724
    public static final class PingBuilder {
725
 
726
        private final Platform platform;
727
 
728
        // in milliseconds
729
        private int waitTime = 4000;
730
        private boolean dontFragment = false;
731
        private int length = -1;
132 ilm 732
        private String srcAddr = null;
93 ilm 733
        private int ttl = -1;
734
        private int totalCount = 4;
735
        private int requiredReplies = -1;
736
 
737
        PingBuilder(final Platform p) {
738
            this.platform = p;
739
        }
740
 
741
        public final PingBuilder setWaitTime(final int waitTime) {
742
            this.waitTime = waitTime;
743
            return this;
744
        }
745
 
746
        public final int getWaitTime() {
747
            return this.waitTime;
748
        }
749
 
750
        public final boolean isDontFragment() {
751
            return this.dontFragment;
752
        }
753
 
754
        public final PingBuilder setDontFragment(boolean dontFragment) {
755
            this.dontFragment = dontFragment;
756
            return this;
757
        }
758
 
759
        public final int getLength() {
760
            return this.length;
761
        }
762
 
763
        public final PingBuilder setLength(int length) {
764
            this.length = length;
765
            return this;
766
        }
767
 
132 ilm 768
        public String getSourceAddress() {
769
            return this.srcAddr;
770
        }
771
 
772
        public PingBuilder setSourceAddress(String srcAddr) {
773
            this.srcAddr = srcAddr;
774
            return this;
775
        }
776
 
93 ilm 777
        public final int getTTL() {
778
            return this.ttl;
779
        }
780
 
781
        public final PingBuilder setTTL(int ttl) {
782
            this.ttl = ttl;
783
            return this;
784
        }
785
 
786
        public final int getTotalCount() {
787
            return this.totalCount;
788
        }
789
 
790
        public final PingBuilder setTotalCount(int totalCount) {
791
            if (totalCount <= 0)
792
                throw new IllegalArgumentException("Negative count : " + totalCount);
793
            this.totalCount = totalCount;
794
            return this;
795
        }
796
 
797
        public final int getRequiredReplies() {
798
            return this.requiredReplies;
799
        }
800
 
801
        public final PingBuilder setRequiredReplies(int requiredReplies) {
802
            this.requiredReplies = requiredReplies;
803
            return this;
804
        }
805
 
132 ilm 806
        public final boolean isAlive(final InetAddress host) throws IOException {
807
            return this.isAlive(host, 0);
93 ilm 808
        }
809
 
132 ilm 810
        public final boolean isAlive(final InetAddress host, final int routingTableIndex) throws IOException {
811
            return execute(host, routingTableIndex).hasRequiredReplies();
812
        }
813
 
814
        public PingResult execute(final InetAddress host) throws IOException {
815
            return execute(host, 0);
816
        }
817
 
818
        public PingResult execute(final InetAddress host, final int routingTableIndex) throws IOException {
93 ilm 819
            return this.platform.ping(host, this, routingTableIndex);
820
        }
821
    }
132 ilm 822
 
823
    public static final class Ping {
824
        public static void main(String[] args) throws IOException {
825
            final int totalCount = Integer.parseInt(System.getProperty("count", "4"));
826
            final String srcAddr = System.getProperty("srcAddr");
827
            System.out.println(Platform.getInstance().createPingBuilder().setTotalCount(totalCount).setSourceAddress(srcAddr).execute(InetAddress.getByName(args[0])));
828
        }
829
    }
93 ilm 830
}