/*----------------------------------------------------------------------------*/
/* e2dirana (ext2fs directory analyse)                                        */
/* Tomas Ericsson, te_tldp@ter.se                                             */
/* v0.3pre, 19 August 2001                                                    */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/* Change log:                                                                */
/*                                                                            */
/* 19 Aug 2001 - v0.3pre                                                      */
/* Creds: Anik Rahman, anik.rahman@gmx.de                                     */
/* Feature add: Option 'o' will list directories only, excluding files.       */
/*                                                                            */
/* 29 Apr 2001 - v0.2.1                                                       */
/* Creds: Sascha Rogmann, info@rogmann.com                                    */
/* Bug fix: e2dirana now can handle one-letter-filenames.                     */
/*                                                                            */
/* 03 Mar 2001 - v0.2                                                         */
/* Creds: Gilbert Roulot, gilbert.roulot@wanadoo.fr                           */
/* Feature add: Option 'd' tries to show deleted files too.                   */
/*                                                                            */
/* 07 Sep 2000 - v0.1                                                         */
/* Initial release.                                                           */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/* Disclaimer:                                                                */
/* The authors is not responsible for any damages made by this program.       */
/* You will do everything at your own risk.                                   */
/*                                                                            */
/* License:                                                                   */
/* GPL, http://www.gnu.org/copyleft/gpl.html                                  */
/*                                                                            */
/* Description:                                                               */
/* This small program has been written to analyse a directory dump created by */
/* debugfs as described in "Ext2fs Undeletion of Directory Structures".       */
/* Use "-h" parameter for usage information.                                  */
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/* Include.                                                                   */
/*----------------------------------------------------------------------------*/

#include <stdio.h>

/*----------------------------------------------------------------------------*/
/* Definitions.                                                               */
/*----------------------------------------------------------------------------*/

#define VERSION "v0.3pre, 19 August 2001"

/*----------------------------------------------------------------------------*/
/* Declaration of functions.                                                  */
/*----------------------------------------------------------------------------*/

int  checkParams(int nrParams, char* params[], int* paramP, int* paramD, int* paramO);
int  openFile(int nrParams, char* params[], FILE** inFile);
void readEntries(FILE* inFile, int paramP, int paramD, int paramO);
void showHelp();

/*----------------------------------------------------------------------------*/
/* Main.                                                                      */
/*----------------------------------------------------------------------------*/

int main(int nrParams, char* params[])
{
    FILE* inFile;
    int paramP = 0;
    int paramD = 0;
    int paramO = 0;
    if (checkParams(nrParams, params, &paramP, &paramD, &paramO))
    {
        return (1);
    }

    if (openFile(nrParams, params, &inFile))
    {
        return (1);
    }

    readEntries(inFile, paramP, paramD, paramO);
    fclose(inFile);

    return (0);
}

/*----------------------------------------------------------------------------*/
/* Check parameters.                                                          */
/*----------------------------------------------------------------------------*/

int checkParams(int nrParams, char* params[], int* paramP, int* paramD, int* paramO)
{
    int i;

    if (nrParams == 1)
    {
        showHelp();
        return (1);
    }

    for (i = 1 ; i < nrParams; i++)
    {
       if (strcmp(params[i], "-h") == 0 || strcmp(params[i], "--help") == 0)
       {
           showHelp();
           return (1);
       }

       if (strcmp(params[i], "-v") == 0 || strcmp(params[i], "--version") == 0)
       {
           printf(VERSION"\n");
           return (1);
       }

       if (strcmp(params[i], "-d") == 0)
       {
           *paramD = 1;
       } 
       else 
       if (strcmp(params[i], "-p") == 0)
       {
           *paramP = 1;
       }
       else
       if (strcmp(params[i], "-o") == 0)
       {
           *paramO = 1;
       }
       else
       if (i != nrParams - 1)
       {
           printf("Invalid option: %s\n", params[i]);
           printf("Use \"-h\" for help.\n");
           return (1);
       }
    }

    return (0);
}

/*----------------------------------------------------------------------------*/
/* Open file.                                                                 */
/*----------------------------------------------------------------------------*/

int openFile(int nrParams, char* params[], FILE** inFile)
{
    *inFile = fopen(params[nrParams - 1], "rb");
    if (!*inFile)
    {
        printf("Unable to open file: %s\n", params[1]);
        return (1);
    }

    return (0);
}

/*----------------------------------------------------------------------------*/
/* Read the directory entries.                                                */
/*----------------------------------------------------------------------------*/

void readEntries(FILE* inFile, int paramP, int paramD, int paramO)
{
    int entryNr = 0;
    unsigned char c;
    int readen;
    unsigned char buf[256];
    int continue_loop = 1;
    int tmp;
   
    fread(&c, 1, 1, inFile); /* Set the EOF-flag if EOF. */
    while ((!feof(inFile)) && (continue_loop))
    {
        int i;

        struct dirEntry
        {
            long int inode;
            long int entryLength;
            int fileNameLength;
            int fileType;
            char fileName[256];
        };

        struct dirEntry de;

        de.inode = 0;
        de.entryLength = 0;
        de.fileNameLength = 0;
        de.fileType = 0;

        fseek(inFile, -1, SEEK_CUR); /* To compensate for the EOF-test. */

        /* The least significant byte comes first */
        for (i = 0 ; i < 4 ; i++)
        {
            int j;
            long int k = 1;
            int loop[] = {4, 2, 1, 1};

            for (j = 0 ; j < loop[i] ; j++)
            {
                fread(&c, 1, 1, inFile);
                switch (i)
                {
                    case 0 : de.inode          += c * k; break;
                    case 1 : de.entryLength    += c * k; break;
                    case 2 : de.fileNameLength += c * k; break;
                    case 3 : de.fileType       += c * k; break;
                }
                k <<= 8;
            }
        }

        fread(de.fileName, de.fileNameLength, 1, inFile);
        de.fileName[de.fileNameLength] = 0;

        /* Case 1,2 : Read past the "." and ".." directory entries. */
        switch (++entryNr)
        {
            case 1  : break;
            case 2  : if (paramP) de.entryLength = 12; break;
            default : if (paramO == 1)
                      {
                          if (de.fileType == 2)
                          {
                              printf("%ld %s\n", de.inode, de.fileName); break;
                          }
                      }
                      else
                      {
                          printf("%ld %s\n", de.inode, de.fileName); break;
                      }
        }

        if (paramD == 0)
        {
            /* Jump to next not-deleted entry. */
            fseek(inFile, de.entryLength - de.fileNameLength - 8, SEEK_CUR);
        }
        else
        {
            /* Search for the next entry (something that looks like one). */
            check:
            do
            {   
                /*
                    Read 9 bytes: 
                    inode (4b) 
                    entrylength (2b)
                    filenamelength (1b)
                    type (1b)
                    first char of name (1b)
                */
                readen = fread(buf, 9, 1, inFile);
            }
            while (
            (
                /* If EOF, do not continue. */
                (readen == 1)
                || (continue_loop = 0)
            )
            &&
            (
                /* If it doesn't look like an entry, continue. */
                ((buf[0] == 0) && (buf[1] == 0) && (buf[2] == 0) && (buf[3] == 0))
                || ((buf[4] + buf[5] * 256) < (buf[6] + 8))
                || (buf[6] == 0)
                || (buf[7] > 7)
                || (buf[8] < 32)
            )
            &&
            (
                /* Go back 8 bytes in the file to try again. */
                fseek(inFile, -8, SEEK_CUR) == 0
            )   
            );

            tmp = buf[6] - 1;

            /* Check to see if one-letter-filename. */
            if (tmp > 0)
            {
                readen = fread(buf, tmp, 1, inFile);
            }
            else
            {
                readen = 1;
            }

            /* Do a check on the bytes in the filename. */
            if (readen != 1) 
            {
                /* Couldn't read it, probably not an entry, it's EOF anyway. */
                continue_loop = 0;
            } 
            else
            {
                /* Check chars. */
                for (i = 0 ; i < tmp ; i++)
                {
                    if (buf[i] < 32)
                    {
                        /* Probably not a filename, try again. */
                        fseek(inFile, -(tmp + 8), SEEK_CUR);
                        goto check;
                    }
                }
            }   
            /* Go back so we can read the entry after looping. */
            fseek(inFile, -(9 + tmp), SEEK_CUR); 
        }

        fread(&c, 1, 1, inFile); /* Set the EOF-flag if EOF. */
    }
}

/*----------------------------------------------------------------------------*/
/* Show help.                                                                 */
/*----------------------------------------------------------------------------*/

void showHelp()
{
    printf("Usage:\n");
    printf("    e2dirana [-p] [-d] [-o] <file>\n");
    printf("    e2dirana [-h] [-v]\n");
    printf("\n");
    printf("Description:\n");
    printf("    <file> = Directory dump from debugfs.\n");
    printf("        -p = Change the pointer at the \"..\"-entry.\n");
    printf("        -d = (Try to) Show deleted files too.\n");
    printf("        -o = Show only directories.\n");
    printf("        -h = Show this help.\n");
    printf("        -v = Show version information.\n");
    printf("\n");
    printf("Output:\n");
    printf("    Entries will be separated by a end-of-line character.\n");
    printf("    The fields are inode number and filename separated by a space.\n");
    printf("\n");
    printf("Authors:\n");
    printf("    Tomas Ericsson, te_tldp@ter.se\n");
    printf("    See http://ter.se/ext2fs-undeletion/ for people that have added\n");
    printf("    features and corrected bugs.\n");
}
