2

I have no idea how swap files are formatted, but I'm wondering if the information in them can be directly examined somehow.

I notice that trying to do this indirectly, by creating an empty file with the same name and/or path and loading it, works, which implies the entire content of the original is in the swap.

According to EvergreenTree's comment below, it also contains meta information such as current process ID, modification timestamps, etc.

  • What are you trying to do? (XY Problem) – mMontu Oct 16 '15 at 11:31
  • @mMontu I don't have an X, lol. I'm just curious. I was wondering about it yesturday because of my previous question, and surprised when I looked around that no one has anything to say about this. It could be that the only real information in there is the file content, in which case this is a bit pointless -- but then so would be the beefy binary format. – goldilocks Oct 16 '15 at 11:41
  • 1
    Swap files do store more information than just the file itself, it stores the user and host name of who last modified it, when it was last modified, and the process ID of the instance of vim that is/was editing that file. – EvergreenTree Oct 16 '15 at 12:19
2

You can find information about the format from the source.

It starts with a block named block0 which holds basic information, see below.

The rest of the file is copies of memory blocks in a tree structure. See description in top of source file.

The base information could easily be extracted. I.e:

$ ./vimswap .foo.swp 
b0_id       6230
Version     VIM 8.0
User        goldilocks
Host        HouseOfCorrection
File        ~goldilocks/devel/foo
inode       2230823
PID         12066
Pagesize    4096
mtime       1572209814
mtime_h     2019-10-27 21:56:54

by something like:

(char_to_long (modified), struct block0 and constants copied form source-code.)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef unsigned char char_u;

#define B0_FNAME_SIZE_ORG   900 /* what it was in older versions */
#define B0_UNAME_SIZE       40
#define B0_HNAME_SIZE       40

struct block0 {
    char_u  b0_id[2];       /* id for block 0: BLOCK0_ID0 and BLOCK0_ID1,
                             * BLOCK0_ID1_C0, BLOCK0_ID1_C1 */
    char_u  b0_version[10]; /* Vim version string */
    char_u  b0_page_size[4];/* number of bytes per page */
    char_u  b0_mtime[4];    /* last modification time of file */
    char_u  b0_ino[4];      /* inode of b0_fname */
    char_u  b0_pid[4];      /* process id of creator (or 0) */
    char_u  b0_uname[B0_UNAME_SIZE]; /* name of user (uid if no name) */
    char_u  b0_hname[B0_HNAME_SIZE]; /* host name (if it has a name) */
    char_u  b0_fname[B0_FNAME_SIZE_ORG]; /* name of file being edited */
    long    b0_magic_long;  /* check for byte order of long */
    int     b0_magic_int;   /* check for byte order of int */
    short   b0_magic_short; /* check for byte order of short */
    char_u  b0_magic_char;  /* check for last char */
};

static long char_to_long(char_u *s) {
    return s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0];
}

char *t_str(long ts, char res[32]) {
    struct tm lt;
    lt = *localtime(&ts);
    strftime(res, 32, "%F %T", &lt);
    return res;
}

void dump_b0(struct block0 b0) {
    int wh = -8;
    char res[32];
    long mtime = char_to_long(b0.b0_mtime);
    /* TAB's added to make for easy shell-parsing */
    fprintf(stdout,
        "%*s\t%02x%02x\n"
        "%*s\t%s\n"

        "%*s\t%s\n"
        "%*s\t%s\n"
        "%*s\t%s\n"
        "%*s\t%ld\n"
        "%*s\t%ld\n"

        "%*s\t%ld\n"
        "%*s\t%ld\n"
        "%*s\t%s\n"
        ,
        wh, "b0_id", b0.b0_id[0], b0.b0_id[1],
        wh, "Version", b0.b0_version,

        wh, "User", b0.b0_uname,
        wh, "Host", b0.b0_hname,
        wh, "File", b0.b0_fname,
        wh, "inode", char_to_long(b0.b0_ino),
        wh, "PID", char_to_long(b0.b0_pid),

        wh, "Pagesize", char_to_long(b0.b0_page_size),
        wh, "mtime", mtime,
        wh, "mtime_h", t_str(mtime, res)
    );
}

int read_block0(char *fn, struct block0 *b0) {
    size_t sz_str = sizeof *b0;
    size_t b_read;
    FILE *fh;

    fh = fopen(fn, "rb");
    if (fh == NULL) {
        fprintf(stderr, "Unable to open `%s'\n", fn);
        return 1;
    }
    b_read = fread(b0, 1, sz_str, fh);
    fclose(fh);
    return b_read != sz_str;
}

int dump_block0(char *fn) {
    struct block0 b0 = {0};
    if (read_block0(fn, &b0) != 0)
        return 1;

    dump_b0(b0);

    return 0;
}

int main(int argc, char *argv[])
{
    int ec = 0;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s SWAP_FILE\n", argv[0]);
        ec = 1;
    } else {
        ec = dump_block0(argv[1]);
    }
    return ec;
}
1

You can directly examine the actual bit-level file contents of any file using hexdump -C.

The -C flag isn't strictly necessary, but it will display an ASCII view of the file side by side with the hexdump, which makes it nice and easy to spot text strings.

I don't know of any better way to examine a non-text file than that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.