1 /*
   2  * Copyright (c) 2017, 2018, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.debug;
  26 
  27 import java.io.File;
  28 import java.io.FileOutputStream;
  29 import java.io.IOException;
  30 import java.nio.file.FileVisitResult;
  31 import java.nio.file.Files;
  32 import java.nio.file.Path;
  33 import java.nio.file.Paths;
  34 import java.nio.file.SimpleFileVisitor;
  35 import java.nio.file.attribute.BasicFileAttributes;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.List;
  39 import java.util.zip.Deflater;
  40 import java.util.zip.ZipEntry;
  41 import java.util.zip.ZipOutputStream;
  42 
  43 import org.graalvm.compiler.options.OptionValues;
  44 import org.graalvm.compiler.serviceprovider.GraalServices;
  45 
  46 /**
  47  * Manages a directory into which diagnostics such crash reports and dumps should be written. The
  48  * directory is archived and deleted when {@link #close()} is called.
  49  */
  50 public class DiagnosticsOutputDirectory {
  51 
  52     /**
  53      * Use an illegal file name to denote that {@link #close()} has been called.
  54      */
  55     private static final String CLOSED = "\u0000";
  56 
  57     public DiagnosticsOutputDirectory(OptionValues options) {
  58         this.options = options;
  59     }
  60 
  61     private final OptionValues options;
  62 
  63     private String path;
  64 
  65     /**
  66      * Gets the path to the output directory managed by this object, creating if it doesn't exist
  67      * and has not been deleted.
  68      *
  69      * @returns the directory or {@code null} if the could not be created or has been deleted
  70      */
  71     public String getPath() {
  72         return getPath(true);
  73     }
  74 
  75     private synchronized String getPath(boolean createIfNull) {
  76         if (path == null && createIfNull) {
  77             path = createPath();
  78             File dir = new File(path).getAbsoluteFile();
  79             if (!dir.exists()) {
  80                 dir.mkdirs();
  81                 if (!dir.exists()) {
  82                     TTY.println("Warning: could not create Graal diagnostic directory " + dir);
  83                     return null;
  84                 }
  85             }
  86         }
  87         if (CLOSED.equals(path)) {
  88             TTY.println("Warning: Graal diagnostic directory already closed");
  89             return null;
  90         }
  91         return path;
  92     }
  93 
  94     /**
  95      * Gets the path of the directory to be created.
  96      *
  97      * Subclasses can override this to determine how the path name is created.
  98      *
  99      * @return the path to be created
 100      */
 101     protected String createPath() {
 102         Path baseDir;
 103         try {
 104             baseDir = DebugOptions.getDumpDirectory(options);
 105         } catch (IOException e) {
 106             // Default to current directory if there was a problem creating the
 107             // directory specified by the DumpPath option.
 108             baseDir = Paths.get(".");
 109         }
 110         return baseDir.resolve("graal_diagnostics_" + GraalServices.getExecutionID()).toAbsolutePath().toString();
 111     }
 112 
 113     /**
 114      * Archives and deletes this directory if it exists.
 115      */
 116     public void close() {
 117         archiveAndDelete();
 118     }
 119 
 120     /**
 121      * Archives and deletes the {@linkplain #getPath() output directory} if it exists.
 122      */
 123     private synchronized void archiveAndDelete() {
 124         String outDir = getPath(false);
 125         if (outDir != null) {
 126             // Notify other threads calling getPath() that the directory is deleted.
 127             // This attempts to mitigate other threads writing to the directory
 128             // while it is being archived and deleted.
 129             path = CLOSED;
 130 
 131             Path dir = Paths.get(outDir);
 132             if (dir.toFile().exists()) {
 133                 String prefix = new File(outDir).getName() + "/";
 134                 File zip = new File(outDir + ".zip").getAbsoluteFile();
 135                 List<Path> toDelete = new ArrayList<>();
 136                 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip))) {
 137                     zos.setLevel(Deflater.BEST_COMPRESSION);
 138                     Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
 139                         @Override
 140                         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 141                             if (attrs.isRegularFile()) {
 142                                 String name = prefix + dir.relativize(file).toString();
 143                                 ZipEntry ze = new ZipEntry(name);
 144                                 zos.putNextEntry(ze);
 145                                 Files.copy(file, zos);
 146                                 zos.closeEntry();
 147                             }
 148                             toDelete.add(file);
 149                             return FileVisitResult.CONTINUE;
 150                         }
 151 
 152                         @Override
 153                         public FileVisitResult postVisitDirectory(Path d, IOException exc) throws IOException {
 154                             toDelete.add(d);
 155                             return FileVisitResult.CONTINUE;
 156                         }
 157                     });
 158                     // Keep this in sync with the catch_files in common.hocon
 159                     TTY.println("Graal diagnostic output saved in %s", zip);
 160                 } catch (IOException e) {
 161                     TTY.printf("IO error archiving %s:%n%s. The directory will not be deleted and must be " +
 162                                     "manually removed once the VM exits.%n", dir, e);
 163                     toDelete.clear();
 164                 }
 165                 if (!toDelete.isEmpty()) {
 166                     IOException lastDeletionError = null;
 167                     for (Path p : toDelete) {
 168                         try {
 169                             Files.delete(p);
 170                         } catch (IOException e) {
 171                             lastDeletionError = e;
 172                         }
 173                     }
 174                     if (lastDeletionError != null) {
 175                         TTY.printf("IO error deleting %s:%n%s. This is most likely due to a compilation on " +
 176                                         "another thread holding a handle to a file within this directory. " +
 177                                         "Please delete the directory manually once the VM exits.%n", dir, lastDeletionError);
 178                     }
 179                 }
 180             }
 181         }
 182     }
 183 }