/* Copyright (C) 2004-2014 Free Software Foundation This file is part of libgcj. This software is copyrighted work licensed under the terms of the Libgcj License. Please consult the file "LIBGCJ_LICENSE" for details. */ package gnu.gcj.tools.gcj_dbtool; import gnu.gcj.runtime.PersistentByteMap; import java.io.*; import java.nio.channels.*; import java.util.*; import java.util.jar.*; import java.security.MessageDigest; public class Main { static private boolean verbose = false; public static void main (String[] s) { boolean fileListFromStdin = false; char filenameSeparator = ' '; insist (s.length >= 1); if (s[0].equals("-") || s[0].equals("-0")) { if (s[0].equals("-0")) filenameSeparator = (char)0; fileListFromStdin = true; String[] newArgs = new String[s.length - 1]; System.arraycopy(s, 1, newArgs, 0, s.length - 1); s = newArgs; } if (s[0].equals("-v") || s[0].equals("--version")) { insist (s.length == 1); System.out.println("gcj-dbtool (" + System.getProperty("java.vm.name") + ") " + System.getProperty("java.vm.version")); System.out.println(); System.out.println("Copyright 2014 Free Software Foundation, Inc."); System.out.println("This is free software; see the source for copying conditions. There is NO"); System.out.println("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."); return; } if (s[0].equals("--help")) { usage(System.out); return; } if (s[0].equals("-n")) { // Create a new database. insist (s.length >= 2 && s.length <= 3); int capacity = 32749; if (s.length == 3) { capacity = Integer.parseInt(s[2]); if (capacity <= 2) { usage(System.err); System.exit(1); } } try { PersistentByteMap b = PersistentByteMap.emptyPersistentByteMap(new File(s[1]), capacity, capacity*32); } catch (Exception e) { System.err.println ("error: could not create " + s[1] + ": " + e.toString()); System.exit(2); } return; } if (s[0].equals("-a") || s[0].equals("-f")) { // Add a jar file to a database, creating it if necessary. // Copies the database, adds the jar file to the copy, and // then renames the new database over the old. try { insist (s.length == 4); File database = new File(s[1]); database = database.getAbsoluteFile(); File jar = new File(s[2]); PersistentByteMap map; if (database.isFile()) map = new PersistentByteMap(database, PersistentByteMap.AccessMode.READ_ONLY); else map = PersistentByteMap.emptyPersistentByteMap(database, 100, 100*32); File soFile = new File(s[3]); if (! s[0].equals("-f") && ! soFile.isFile()) throw new IllegalArgumentException(s[3] + " is not a file"); map = addJar(jar, map, soFile); } catch (Exception e) { System.err.println ("error: could not update " + s[1] + ": " + e.toString()); System.exit(2); } return; } if (s[0].equals("-t")) { // Test try { insist (s.length == 2); PersistentByteMap b = new PersistentByteMap(new File(s[1]), PersistentByteMap.AccessMode.READ_ONLY); Iterator iterator = b.iterator(PersistentByteMap.ENTRIES); while (iterator.hasNext()) { PersistentByteMap.MapEntry entry = (PersistentByteMap.MapEntry)iterator.next(); byte[] key = (byte[])entry.getKey(); byte[] value = (byte[])b.get(key); if (! Arrays.equals (value, (byte[])entry.getValue())) { String err = ("Key " + bytesToString(key) + " at bucket " + entry.getBucket()); throw new RuntimeException(err); } } } catch (Exception e) { e.printStackTrace(); System.exit(3); } return; } if (s[0].equals("-m")) { // Merge databases. insist (s.length >= 3 || fileListFromStdin && s.length == 2); try { File database = new File(s[1]); database = database.getAbsoluteFile(); File temp = File.createTempFile(database.getName(), "", database.getParentFile()); int newSize = 0; int newStringTableSize = 0; Fileset files = getFiles(s, 2, fileListFromStdin, filenameSeparator); PersistentByteMap[] sourceMaps = new PersistentByteMap[files.size()]; // Scan all the input files, calculating worst case string // table and hash table use. { Iterator it = files.iterator(); int i = 0; while (it.hasNext()) { PersistentByteMap b = new PersistentByteMap((File)it.next(), PersistentByteMap.AccessMode.READ_ONLY); newSize += b.size(); newStringTableSize += b.stringTableSize(); sourceMaps[i++] = b; } } newSize *= 1.5; // Scaling the new size by 1.5 results in // fewer collisions. PersistentByteMap map = PersistentByteMap.emptyPersistentByteMap (temp, newSize, newStringTableSize); for (int i = 0; i < sourceMaps.length; i++) { if (verbose) System.err.println("adding " + sourceMaps[i].size() + " elements from " + sourceMaps[i].getFile()); map.putAll(sourceMaps[i]); } map.close(); temp.renameTo(database); } catch (Exception e) { e.printStackTrace(); System.exit(3); } return; } if (s[0].equals("-l")) { // List a database. insist (s.length == 2); try { PersistentByteMap b = new PersistentByteMap(new File(s[1]), PersistentByteMap.AccessMode.READ_ONLY); System.out.println ("Capacity: " + b.capacity()); System.out.println ("Size: " + b.size()); System.out.println (); System.out.println ("Elements: "); Iterator iterator = b.iterator(PersistentByteMap.ENTRIES); while (iterator.hasNext()) { PersistentByteMap.MapEntry entry = (PersistentByteMap.MapEntry)iterator.next(); byte[] digest = (byte[])entry.getKey(); System.out.print ("[" + entry.getBucket() + "] " + bytesToString(digest) + " -> "); System.out.println (new String((byte[])entry.getValue())); } } catch (Exception e) { System.err.println ("error: could not list " + s[1] + ": " + e.toString()); System.exit(2); } return; } if (s[0].equals("-d")) { // For testing only: fill the byte map with random data. insist (s.length == 2); try { MessageDigest md = MessageDigest.getInstance("MD5"); PersistentByteMap b = new PersistentByteMap(new File(s[1]), PersistentByteMap.AccessMode.READ_WRITE); int N = b.capacity(); byte[] bytes = new byte[1]; byte digest[] = md.digest(bytes); for (int i = 0; i < N; i++) { digest = md.digest(digest); b.put(digest, digest); } } catch (Exception e) { e.printStackTrace(); System.exit(3); } return; } if (s[0].equals("-p")) { insist (s.length == 1 || s.length == 2); String result; if (s.length == 1) result = System.getProperty("gnu.gcj.precompiled.db.path", ""); else result = (s[1] + (s[1].endsWith(File.separator) ? "" : File.separator) + getDbPathTail ()); System.out.println (result); return; } usage(System.err); System.exit(1); } private static native String getDbPathTail (); private static void insist(boolean ok) { if (! ok) { usage(System.err); System.exit(1); } } private static void usage(PrintStream out) { out.println ("gcj-dbtool: Manipulate gcj map database files\n" + "\n" + " Usage: \n" + " gcj-dbtool -n file.gcjdb [size] - Create a new gcj map database\n" + " gcj-dbtool -a file.gcjdb file.jar file.so\n" + " - Add the contents of file.jar to a gcj map database\n" + " gcj-dbtool -f file.gcjdb file.jar file.so\n" + " - Add the contents of file.jar to a gcj map database\n" + " gcj-dbtool -t file.gcjdb - Test a gcj map database\n" + " gcj-dbtool -l file.gcjdb - List a gcj map database\n" + " gcj-dbtool [-][-0] -m dest.gcjdb [source.gcjdb]...\n" + " - Merge gcj map databases into dest\n" + " Replaces dest\n" + " To add to dest, include dest in the list of sources\n" + " If the first arg is -, read the list from stdin\n" + " If the first arg is -0, filenames separated by nul\n" + " gcj-dbtool -p [LIBDIR] - Print default database name" ); } // Add a jar to a map. This copies the map first and returns a // different map that contains the data. The original map is // closed. private static PersistentByteMap addJar(File f, PersistentByteMap b, File soFile) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); JarFile jar = new JarFile (f); int count = 0; { Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry classfile = (JarEntry)entries.nextElement(); if (classfile.getName().endsWith(".class")) count++; } } if (verbose) System.err.println("adding " + count + " elements from " + f + " to " + b.getFile()); // Maybe resize the destination map. We're allowing plenty of // extra space by using a loadFactor of 2. b = resizeMap(b, (b.size() + count) * 2, true); Enumeration entries = jar.entries(); byte[] soFileName = soFile.getCanonicalPath().getBytes("UTF-8"); while (entries.hasMoreElements()) { JarEntry classfile = (JarEntry)entries.nextElement(); if (classfile.getName().endsWith(".class")) { InputStream str = jar.getInputStream(classfile); int length = (int) classfile.getSize(); if (length == -1) throw new EOFException(); byte[] data = new byte[length]; int pos = 0; while (length - pos > 0) { int len = str.read(data, pos, length - pos); if (len == -1) throw new EOFException("Not enough data reading from: " + classfile.getName()); pos += len; } b.put(md.digest(data), soFileName); } } return b; } // Resize a map by creating a new one with the same data and // renaming it. If close is true, close the original map. static PersistentByteMap resizeMap(PersistentByteMap m, int newCapacity, boolean close) throws IOException, IllegalAccessException { newCapacity = Math.max(m.capacity(), newCapacity); File name = m.getFile(); File copy = File.createTempFile(name.getName(), "", name.getParentFile()); try { PersistentByteMap dest = PersistentByteMap.emptyPersistentByteMap (copy, newCapacity, newCapacity*32); dest.putAll(m); dest.force(); if (close) m.close(); copy.renameTo(name); return dest; } catch (Exception e) { copy.delete(); } return null; } static String bytesToString(byte[] b) { StringBuffer hexBytes = new StringBuffer(); int length = b.length; for (int i = 0; i < length; ++i) { int v = b[i] & 0xff; if (v < 16) hexBytes.append('0'); hexBytes.append(Integer.toHexString(v)); } return hexBytes.toString(); } // Return a Fileset, either from a String array or from System.in, // depending on fileListFromStdin. private static final Fileset getFiles(String[] s, int startPos, boolean fileListFromStdin, char separator) { if (fileListFromStdin) return new Fileset(System.in, separator); else return new Fileset(s, startPos, s.length); } } // Parse a stream into tokens. The separator can be any char, and // space is equivalent to any whitepace character. class Tokenizer { final Reader r; final char separator; Tokenizer(Reader r, char separator) { this.r = r; this.separator = separator; } boolean isSeparator(int c) { if (Character.isWhitespace(separator)) return Character.isWhitespace((char)c); else return c == separator; } // Parse a token from the input stream. Return the empty string // when the stream is exhausted. String nextToken () { StringBuffer buf = new StringBuffer(); int c; try { while ((c = r.read()) != -1) { if (! isSeparator(c)) { buf.append((char)c); break; } } while ((c = r.read()) != -1) { if (isSeparator(c)) break; else buf.append((char)c); } } catch (java.io.IOException e) { } return buf.toString(); } } // A Fileset is a container for a set of files; it can be created // either from a string array or from an input stream, given a // separator character. class Fileset { LinkedHashSet files = new LinkedHashSet(); Fileset (String[] s, int start, int end) { for (int i = start; i < end; i++) { files.add(new File(s[i])); } } Fileset (InputStream is, char separator) { Reader r = new BufferedReader(new InputStreamReader(is)); Tokenizer st = new Tokenizer(r, separator); String name; while (! "".equals(name = st.nextToken())) files.add(new File(name)); } Iterator iterator() { return files.iterator(); } int size() { return files.size(); } }