Search code examples
javafileobjectoutputstream

Can I recover a file whose ObjectOutputStream was never closed?


Our application needs to write to a file using an ObjectOutputStream. There was a bug that would result in the stream occasionally not getting closed. Then any subsequent attempts to create the ObjectInputStream from the file would result in an EOF Exception. We've fixed the bug, but anyone affected by this would still need to recreate the file from scratch. Is there a way we could recover from this without recreating it?

I've tried closing a new Output Stream that was created from the same file, but that results in a different EOF Exception whenever I attempt to read from the file.

I run it once to create the file with load(), insert data with save(), and insert data without closing with save_damage().

That fails with the following exception (as I'd expect):

java.io.EOFException: Unexpected end of ZLIB input stream
    at java.util.zip.InflaterInputStream.fill(InflaterInputStream.java:240)
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158)
    at java.util.zip.GZIPInputStream.read(GZIPInputStream.java:117)
    at java.io.ObjectInputStream$PeekInputStream.read(ObjectInputStream.java:2779)
    at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2795)
    at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3272)
    at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:932)
    at java.io.ObjectInputStream.<init>(ObjectInputStream.java:394)
    at TestProgram.load(TestProgram.java:47)
    at TestProgram.main(TestProgram.java:24)

That simulates the bug we used to have. Now I'd like to find a way to restore the config file without recreating it from scratch. I attempted to do that by closing it with close_damaged_file() at the start. However, after uncommenting that line I get the following error:

java.io.EOFException
    at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:3076)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1616)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)
    at TestProgram.load(TestProgram.java:48)
    at TestProgram.main(TestProgram.java:19)

Source Below:

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class TestProgram {
    private final static String FILE_PATH = "./config.ser";
    private static String config;

  public static void main(String[] args) {
      try {
        // close_damaged_file();
        load();
        save("Test Data");
        load();
        System.out.println(config);
        save_damage("Damaged Data");
        load();
      } catch(Exception e) {
        e.printStackTrace(System.out);
        return;
      } 
      System.out.println(config);
  }

  public static boolean load() throws IOException, ClassNotFoundException {
    File file = new File(FILE_PATH);
    if (!file.exists()) {
        file.createNewFile();
        save("");
        file.setReadable(true, false);    //set authority to 666
        file.setWritable(true, false);
        file.setExecutable(false, false);
    }
    InputStream inputStream = null;
    GZIPInputStream gZipStream = null;
    ObjectInputStream objInputStream = null;
    try {
        inputStream = new BufferedInputStream(new FileInputStream(file));
        gZipStream = new GZIPInputStream(inputStream);
        objInputStream = new ObjectInputStream(gZipStream);
        config = (String) objInputStream.readObject();
    } finally {
        if (inputStream != null) 
            inputStream.close();
        if (gZipStream != null)
            gZipStream.close();
        if (objInputStream != null)
            objInputStream.close();
    }
    return true;
  }
  public static boolean save(String data) throws IOException {
    FileOutputStream outputStream = new FileOutputStream(FILE_PATH);
    GZIPOutputStream gZipStream = new GZIPOutputStream(outputStream);
    ObjectOutputStream objOutputStream = new ObjectOutputStream(gZipStream);
    objOutputStream.writeObject(data);
    objOutputStream.flush();
    objOutputStream.close();
    return true;
  }
  public static boolean save_damage(String data) throws IOException {
    FileOutputStream outputStream = new FileOutputStream(FILE_PATH);
    GZIPOutputStream gZipStream = new GZIPOutputStream(outputStream);
    ObjectOutputStream objOutputStream = new ObjectOutputStream(gZipStream);
    objOutputStream.writeObject(data);
    return true;
  }
  public static boolean close_damaged_file() throws IOException {
    FileOutputStream fOut= null;
    GZIPOutputStream gZip = null;
    ObjectOutputStream outStream = null;
    try {
        fOut = new FileOutputStream(FILE_PATH);
        gZip = new GZIPOutputStream(fOut);
        outStream = new ObjectOutputStream(gZip);
        outStream.flush();
    } finally {
        outStream.close();
    }
    return true;
  }

}

Solution

  • Maybe you can recover the file manually, the serialization format is described in the Java Object Serialization Specification.

    But first you would have to recover the broken gzip file that you created. You can try to decompress it with the gzip command itself and according to the man page of gzip, you can try to recover a damaged file with something like zcat file > recover.

    Then you can either deserialize the file recover without the GZipInputStream or bring you favourite hex editor, open the file and start analyzing it manually with the help of the docs in the serialization specs. Maybe you can create a program to recover objects until a failure from that file, but individual advice is probably not possible without doing the actual work on the actual file.

    Good luck!