/*
*   This file released to public domain by the copyright holders 
*   including ArrayComm, Inc.
*   Author: Ted Merrill
*/

/* decr.c -- Program to remove (or add) CR characters from files, in place. */

char * HelpLines[] =
{
"NAME   ",
"        decr -- Program to remove (or add) CR chars from files, in place. ",
"  ",
"SYNOPSIS  ",
"        decr [-add] [-binok] [-f] <file>...  ",
"        decr -h              -- for this message.   ",
"  ",
"DESCRIPTION  ",
"------------------------------------------------------------------------------  ",
"Removes (or adds) CR characters from files.  ",
"Such characters are added in by MS-DOS editors, since the Micrsoft ",
"standard is terminate each line with CR, NL (whereas Unix uses only NL). ",
"  ",
"The -f option allows disenables checking for options for all succeeding  ",
"arguments, thus allowing filenames beginning with a hyphen to be handled.  ",
"  ",
"Only regular files that have write permission enabled are processed.  ",
"The files are overwritten in place, but only if they need to be changed.  ",
"(Actually, the program writes a temp file, and then does rename  ",
"to move it to the desired name.  ",
"Thus this will fail if you don't have directory write permission.)  ",
"  ",
"If a file does not need to be changed, it is not altered.  ",
"For each eligible file, if the file contained a CR (was missing a CR),  ",
"the line is written to the stdout:  ",
"    Removed CRs from <file>     or    Added CRs to <file> ",
"Otherwise (for eligible files), this output line is written:  ",
"    No CRs in <file>            or    No Missing CRs in <file>  ",
"Ineligible files (e.g. directories, readonly files, device files, etc.)  ",
"are indicated by various messages.  ",
"Binary files (files with a byte with high bit set) are not eligible unless ",
"the -binok option is used.  ",
"  ",
"Locks out QUIT and INT signals during each file operation,   ",
"aborting only between files.  ",
"However, it is possible that a system failure may leave a harmless  ",
"temporary file in the same directory as the target file.  ",
"This program uses the rename system call, which should atomically  "
"replace the old file with the processed file.  ",
0       /* terminator */
};

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>

int BinaryOk = 0;       /* 0 = don't change binary files, 1 = do change them */
int addcrs = 0;         /* 0 = remove CRs, 1 = add CRs */
volatile int signalCaught = 0;
void signalHandler(int unused)
{
    signalCaught = 1;   /* flag for later */
}

void PrintHelp(void)
{
    int Index;
    for ( Index = 0; HelpLines[Index]; Index++ )
        printf("%s\n", HelpLines[Index] );
}

int decrInner( FILE *inFile, FILE *outFile )
{   /* returns -1 if binary file, < -1 if error, 0 if no change, >0 if change*/
    int Ch;
    int LastCh = 0;
    int changeFlag = 0;        /* nonzero when we get at least one cr */
    for ( ; (Ch = getc(inFile)) != EOF; LastCh = Ch )
    {
        if ( ! BinaryOk && (Ch & 0x80) != 0 )
        {   /* apparent binary character */
            return -1;
        }
        if ( Ch == '\n' )
        {   /* newline */
            if ( addcrs && LastCh != '\r' )
            {
                putc('\r',outFile);
                changeFlag = 1;
            }
            putc(Ch,outFile);
            /* Check occasionally for abort... */
            if ( signalCaught ) return -2;
        }
        else
        if ( (! addcrs) && Ch == '\r' )
        {
            /* don't put the CR we are removing */
            changeFlag = 1;
        }
        else
        {   /* non-cr, non-newline*/
            putc(Ch,outFile);
        }
    }
    return changeFlag;
}

void decrfile(char *filepath)
{
    char newpath[512];
    FILE *inFile = 0;
    FILE *outFile = 0;
    int nReturn;
    int nFileMode;
    struct stat statbuf;

    if ( lstat(filepath,&statbuf) )
    {
        fprintf(stderr,"Not found: %s\n", filepath );
        return;
    }
    if ( S_ISLNK(statbuf.st_mode) )
    {
        /* Note: if we DID want to decr a soft link, perhaps the file should
        *   be processed in its "true" directory!
        */
        printf("Not processing soft link: %s\n", filepath );
        return;
    }
    if ( S_ISDIR(statbuf.st_mode) )
    {
        printf("Not processing directory: %s\n", filepath );
        return;
    }
    if ( ! S_ISREG(statbuf.st_mode) )
    {
        printf("Not processing device/pipe/socket file: %s\n", filepath );
        return;
    }
    if ( access( filepath, W_OK ) )
    {
        /* NOTE: without this check, this program does a fine job
        *   of processing readonly files, provided the directory is readable.
        */
        printf("Not processing readonly file: %s\n", filepath );
        return;
    }

    /* Ignore signals */
    signal(SIGQUIT,signalHandler);
    signal(SIGINT,signalHandler);

    sprintf(newpath,"%s__temp_decr",filepath);
    inFile = fopen(filepath,"r");
    if ( ! inFile )
    {
        fprintf(stderr, "Could not open %s for reading.\n", filepath);
        exit(1);
    }
    if (fstat(fileno(inFile), &statbuf))
    {
        fprintf(stderr, "Stat error on %s\n", filepath);
        exit(1);
    }
    nFileMode = statbuf.st_mode;
    outFile = fopen(newpath,"w");
    if ( ! outFile )
    {
        fprintf(stderr, "Could not create %s .\n", newpath);
        exit(1);
    }
    nReturn = decrInner(inFile,outFile);
    if ( nReturn == -1 )
    {
        printf("Not processing binary file %s\n", filepath );
        fclose(inFile);
        fclose(outFile);
        unlink(newpath);
        goto CleanUpSignals;
    }
    else
    if ( nReturn < -1 )
    {   /* error -- already reported */
        fclose(inFile);
        fclose(outFile);
        unlink(newpath);
        fprintf(stderr,"Abort.\n");
        exit(1);
    }
    else
    if ( nReturn == 0 )
    {   /* no change */
        if ( addcrs ) printf("No Missing CRs in %s\n", filepath );
        else printf("No CRs in %s\n", filepath );
        fclose(inFile);
        fclose(outFile);
        unlink(newpath);
        goto CleanUpSignals;
    }
    /* else CRs were removed */
    if ( ferror(inFile ) )
    {
        fprintf(stderr, "Read error on %s .\n", filepath );
        fclose(inFile);
        fclose(outFile);
        unlink(newpath);
        exit(1);
    }
    fclose(inFile);
    if ( ferror(outFile ) )
    {
        fprintf(stderr, "Write error on %s .\n", newpath );
        fclose(outFile);
        unlink(newpath);
        exit(1);
    }
    fclose(outFile);
    if ( chmod(newpath,nFileMode) )
    {
        fprintf(stderr, "Could not change mode of %s to 0%o\n",
                    newpath, nFileMode );
        unlink(newpath);
        exit(1);
    }
    if ( rename(newpath,filepath) )
    {   /* This really shouldn't fail at this point, but... */
        fprintf(stderr, "Could not rename %s to %s\n", newpath, filepath );
        fprintf(stderr, "Unprocessed file should be in %s\n", filepath );
        fprintf(stderr, "Processed file should be in %s\n", newpath );
        fprintf(stderr, "Please check your files...\n");
        exit(1);
    }
    if ( addcrs ) printf("Added CRs to %s\n", filepath );
    else printf("Removed CRs from %s\n", filepath );

    CleanUpSignals:
    if ( signalCaught ) 
    {
        fprintf(stderr, "Abort!\n");
        exit(1);
    }
    /* Reenable signals */
    signal(SIGQUIT,SIG_DFL);
    signal(SIGINT,SIG_DFL);
    return;
}

int main(int argc, char **argv)
{
    int nfiles = 0;  /* no. of files processed */
    int checkdash = 1;  /* processing options enabled? */
    char *arg;
    for ( argv++; (arg = *argv) != NULL; argv++ )
    {
        if ( checkdash && *arg == '-' )
        {
            if ( ! strcmp( arg, "-h" )) 
            {
                PrintHelp();
                exit(0);
            }
            else
            if ( ! strcmp( arg, "-f" )) 
            {
                checkdash = 0;
            }
            else
            if ( ! strcmp( arg, "-binok" )) 
            {
                BinaryOk = 1;
            }
            else
            if ( ! strcmp( arg, "-add" ))
            {
                addcrs = 1;
            }
            else
            {
                fprintf(stderr, "Invalid arg (use -h for help): %s\n", arg);
                exit(1);
            }
        }
        else
        {
            decrfile(arg);
            nfiles++;
        }
    }
    if ( ! nfiles ) printf("No files were specified; use -h for help.\n");
    exit(0);
    return 0;
}
