/*	$NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $	*/
/*	$NetBSD: misc.c,v 1.11 2007/08/22 16:59:19 christos Exp $	*/
/*	$NetBSD: regular.c,v 1.20 2006/06/03 21:47:55 christos Exp $	*/
/*	$NetBSD: special.c,v 1.12 2007/08/21 14:09:54 christos Exp $	*/

/*
 * Copyright (c) 1987, 1990, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\n\
	The Regents of the University of California.  All rights reserved.\n");*/

#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#if defined(__FreeBSD__) || defined(__NetBSD__) /** @todo more mmap capable OSes. */
# define CMP_USE_MMAP
# include <sys/param.h>
# include <sys/mman.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
# include <unistd.h>
# ifndef O_BINARY
#  define O_BINARY 0
# endif
#else
# define MSC_DO_64_BIT_IO
# include "mscfakes.h"
#endif
#include "err.h"

#include "cmp_extern.h"


static int
errmsg(const char *file, off_t byte, off_t line, int lflag)
{
    if (lflag)
#ifdef _MSC_VER
        return err(ERR_EXIT, "%s: char %I64d, line %lld", file, (__int64)byte, (long long)line);
#else
        return err(ERR_EXIT, "%s: char %lld, line %lld", file, (long long)byte, (long long)line);
#endif
    return err(ERR_EXIT, "%s", file);
}


static int
eofmsg(const char *file, off_t byte, off_t line, int sflag, int lflag)
{
    if (!sflag)
    {
        if (!lflag)
            warnx("EOF on %s", file);
        else
        {
#ifdef _MSC_VER
            if (line > 0)
                warnx("EOF on %s: char %I64d, line %I64d", file, (__int64)byte, (__int64)line);
            else
                warnx("EOF on %s: char %I64d", file, (__int64)byte);
#else
            if (line > 0)
                warnx("EOF on %s: char %lld, line %lld", file, (long long)byte, (long long)line);
            else
                warnx("EOF on %s: char %lld", file, (long long)byte);
#endif
        }
    }
    return DIFF_EXIT;
}


static int
diffmsg(const char *file1, const char *file2, off_t byte, off_t line, int sflag)
{
    if (!sflag)
#ifdef _MSC_VER
        printf("%s %s differ: char %I64d, line %I64d\n",
               file1, file2, (__int64)byte, (__int64)line);
#else
        printf("%s %s differ: char %lld, line %lld\n",
               file1, file2, (long long)byte, (long long)line);
#endif
    return DIFF_EXIT;
}


/**
 * Compares two files, where one or both are non-regular ones.
 */
static int
c_special(int fd1, const char *file1, off_t skip1,
          int fd2, const char *file2, off_t skip2,
          int lflag, int sflag)
{
    int fd1dup, fd2dup;
    FILE *fp1;
    int rc;

    /* duplicate because fdopen+fclose will otherwise close the handle. */
    fd1dup = dup(fd1);
    if (fd1 < 0)
        return err(ERR_EXIT, "%s", file1);
    fp1 = fdopen(fd1dup, "rb");
    if (!fp1)
        fp1 = fdopen(fd1dup, "r");
    if (!fp1)
    {
        err(ERR_EXIT, "%s", file1);
        close(fd1dup);
        return ERR_EXIT;
    }

    fd2dup = dup(fd2);
    if (fd2dup >= 0)
    {
        FILE *fp2 = fdopen(fd2dup, "rb");
        if (!fp2)
            fp2 = fdopen(fd2dup, "r");
        if (fp2)
        {
            off_t byte;
            off_t line;
            int ch1 = 0;
            int ch2 = 0;

            /* skipping ahead */
            rc = OK_EXIT;
            for (byte = line = 1; skip1--; byte++)
            {
                ch1 = getc(fp1);
                if (ch1 == EOF)
                    break;
                if (ch1 == '\n')
                    line++;
            }
            for (byte = line = 1; skip2--; byte++)
            {
                ch2 = getc(fp2);
                if (ch2 == EOF)
                    break;
                if (ch2 == '\n')
                    line++;
            }
            if (ch2 != EOF && ch1 != EOF)
            {
                /* compare byte by byte */
                for (byte = line = 1;; ++byte)
                {
                    ch1 = getc(fp1);
                    ch2 = getc(fp2);
                    if (ch1 == EOF || ch2 == EOF)
                        break;
                    if (ch1 != ch2)
                    {
                        if (!lflag)
                        {
                            rc = diffmsg(file1, file2, byte, line, sflag);
                            break;
                        }
                        rc = DIFF_EXIT;
#ifdef _MSC_VER
                        printf("%6i64d %3o %3o\n", (__int64)byte, ch1, ch2);
#else
                        printf("%6lld %3o %3o\n", (long long)byte, ch1, ch2);
#endif
                    }
                    if (ch1 == '\n')
                        ++line;
                }
            }

            /* Check for errors and length differences (EOF). */
            if (ferror(fp1) && rc != ERR_EXIT)
                rc = errmsg(file1, byte, line, lflag);
            if (ferror(fp2) && rc != ERR_EXIT)
                rc = errmsg(file2, byte, line, lflag);
            if (rc == OK_EXIT)
            {
                if (feof(fp1))
                {
                    if (!feof(fp2))
                        rc = eofmsg(file1, byte, line, sflag, lflag);
                }
                else if (feof(fp2))
                    rc = eofmsg(file2, byte, line, sflag, lflag);
            }

            fclose(fp2);
        }
        else
        {
            rc = err(ERR_EXIT, "%s", file2);
            close(fd2dup);
        }
    }
    else
        rc = err(ERR_EXIT, "%s", file2);

    fclose(fp1);
    return rc;
}


#ifdef CMP_USE_MMAP
/**
 * Compare two files using mmap.
 */
static int
c_regular(int fd1, const char *file1, off_t skip1, off_t len1,
          int fd2, const char *file2, off_t skip2, off_t len2, int sflag, int lflag)
{
    unsigned char ch, *p1, *p2, *b1, *b2;
    off_t byte, length, line;
    int dfound;
    size_t blk_sz, blk_cnt;

    if (sflag && len1 != len2)
        return DIFF_EXIT;

    if (skip1 > len1)
        return eofmsg(file1, len1 + 1, 0, sflag, lflag);
    len1 -= skip1;
    if (skip2 > len2)
        return eofmsg(file2, len2 + 1, 0, sflag, lflag);
    len2 -= skip2;

    byte = line = 1;
    dfound = 0;
    length = len1 <= len2 ? len1 : len2;
    for (blk_sz = 1024 * 1024; length != 0; length -= blk_sz)
    {
        if (blk_sz > length)
            blk_sz = length;
        b1 = p1 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE | MAP_SHARED, fd1, skip1);
        if (p1 == MAP_FAILED)
            goto l_mmap_failed;

        b2 = p2 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE | MAP_SHARED, fd2, skip2);
        if (p2 == MAP_FAILED)
        {
            munmap(p1, blk_sz);
            goto l_mmap_failed;
        }

        blk_cnt = blk_sz;
        for (; blk_cnt--; ++p1, ++p2, ++byte)
        {
            if ((ch = *p1) != *p2)
            {
                if (!lflag)
                {
                    munmap(b1, blk_sz);
                    munmap(b2, blk_sz);
                    return diffmsg(file1, file2, byte, line, sflag);
                }
                dfound = 1;
#ifdef _MSC_VER
                printf("%6I64d %3o %3o\n", (__int64)byte, ch, *p2);
#else
                printf("%6lld %3o %3o\n", (long long)byte, ch, *p2);
#endif
            }
            if (ch == '\n')
                ++line;
        }
        munmap(p1 - blk_sz, blk_sz);
        munmap(p2 - blk_sz, blk_sz);
        skip1 += blk_sz;
        skip2 += blk_sz;
    }

    if (len1 != len2)
        return eofmsg(len1 > len2 ? file2 : file1, byte, line, sflag, lflag);
    if (dfound)
        return DIFF_EXIT;
    return OK_EXIT;

l_mmap_failed:
    return c_special(fd1, file1, skip1, fd2, file2, skip2, lflag, sflag);
}

#else /* non-mmap c_regular: */

/**
 * Compare two files without mmaping them.
 */
static int
c_regular(int fd1, const char *file1, off_t skip1, off_t len1,
          int fd2, const char *file2, off_t skip2, off_t len2, int sflag, int lflag)
{
    unsigned char ch, *p1, *p2, *b1 = 0, *b2 = 0;
    off_t byte, length, line, bytes_read;
    int dfound;
    size_t blk_sz, blk_cnt;

    if (sflag && len1 != len2)
        return DIFF_EXIT;

    if (skip1 > len1)
        return eofmsg(file1, len1 + 1, 0, sflag, lflag);
    len1 -= skip1;
    if (skip2 > len2)
        return eofmsg(file2, len2 + 1, 0, sflag, lflag);
    len2 -= skip2;

    if (skip1 && lseek(fd1, skip1, SEEK_SET) < 0)
        goto l_special;
    if (skip2 && lseek(fd2, skip2, SEEK_SET) < 0)
    {
        if (skip1 && lseek(fd1, 0, SEEK_SET) < 0)
            return err(1, "seek failed");
        goto l_special;
    }

#define CMP_BUF_SIZE (128*1024)

    b1 = malloc(CMP_BUF_SIZE);
    b2 = malloc(CMP_BUF_SIZE);
    if (!b1 || !b2)
        goto l_malloc_failed;

    byte = line = 1;
    dfound = 0;
    length = len1;
    if (length > len2)
        length = len2;
    for (blk_sz = CMP_BUF_SIZE; length != 0; length -= blk_sz)
    {
        if ((off_t)blk_sz > length)
            blk_sz = length;

        bytes_read = read(fd1, b1, blk_sz);
        if (bytes_read != (off_t)blk_sz)
            goto l_read_error;

        bytes_read = read(fd2, b2, blk_sz);
        if (bytes_read != (off_t)blk_sz)
            goto l_read_error;

        blk_cnt = blk_sz;
        p1 = b1;
        p2 = b2;
        for (; blk_cnt--; ++p1, ++p2, ++byte)
        {
            if ((ch = *p1) != *p2)
            {
                if (!lflag)
                {
                    free(b1);
                    free(b2);
                    return diffmsg(file1, file2, byte, line, sflag);
                }
                dfound = 1;
#ifdef _MSC_VER
                printf("%6I64d %3o %3o\n", (__int64)byte, ch, *p2);
#else
                printf("%6lld %3o %3o\n", (long long)byte, ch, *p2);
#endif
            }
            if (ch == '\n')
                ++line;
        }
        skip1 += blk_sz;
        skip2 += blk_sz;
    }

    if (len1 != len2)
        return eofmsg(len1 > len2 ? file2 : file1, byte, line, sflag, lflag);
    if (dfound)
        return DIFF_EXIT;
    return OK_EXIT;

l_read_error:
    if (    lseek(fd1, 0, SEEK_SET) < 0
        ||  lseek(fd2, 0, SEEK_SET) < 0)
    {
        err(1, "seek failed");
        free(b1);
        free(b2);
        return 1;
    }
l_malloc_failed:
    free(b1);
    free(b2);
l_special:
    return c_special(fd1, file1, skip1, fd2, file2, skip2, lflag, sflag);
}
#endif  /* non-mmap c_regular */


/**
 * Compares two open files.
 */
int
cmp_fd_and_fd_ex(int fd1, const char *file1, off_t skip1,
                 int fd2, const char *file2, off_t skip2,
                 int sflag, int lflag, int special)
{
    struct stat st1, st2;
    int rc;

    if (fstat(fd1, &st1))
        return err(ERR_EXIT, "%s", file1);
    if (fstat(fd2, &st2))
        return err(ERR_EXIT, "%s", file2);

    if (    !S_ISREG(st1.st_mode)
        ||  !S_ISREG(st2.st_mode)
        ||  special)
        rc = c_special(fd1, file1, skip1,
                       fd2, file2, skip2, sflag, lflag);
    else
        rc = c_regular(fd1, file1, skip1, st1.st_size,
                       fd2, file2, skip2, st2.st_size, sflag, lflag);
    return rc;
}


/**
 * Compares two open files.
 */
int
cmp_fd_and_fd(int fd1, const char *file1,
              int fd2, const char *file2,
              int sflag, int lflag, int special)
{
    return cmp_fd_and_fd_ex(fd1, file1, 0, fd2, file2, 0, sflag, lflag, special);
}


/**
 * Compares an open file with another that isn't open yet.
 */
int
cmp_fd_and_file_ex(int fd1, const char *file1, off_t skip1,
                   const char *file2, off_t skip2,
                   int sflag, int lflag, int special)
{
    int rc;
    int fd2;

    if (!strcmp(file2, "-"))
    {
        fd2 = 0 /* stdin */;
        special = 1;
        file2 = "stdin";
    }
    else
        fd2 = open(file2, O_RDONLY | O_BINARY, 0);
    if (fd2 >= 0)
    {
        rc = cmp_fd_and_fd_ex(fd1, file1, skip1,
                              fd2, file2, skip2, sflag, lflag, special);
        close(fd2);
    }
    else
    {
        if (!sflag)
            warn("%s", file2);
        rc = ERR_EXIT;
    }
    return rc;
}


/**
 * Compares an open file with another that isn't open yet.
 */
int
cmp_fd_and_file(int fd1, const char *file1,
                const char *file2,
                int sflag, int lflag, int special)
{
    return cmp_fd_and_file_ex(fd1, file1, 0,
                                   file2, 0, sflag, lflag, special);
}


/**
 * Opens and compare two files.
 */
int
cmp_file_and_file_ex(const char *file1, off_t skip1,
                     const char *file2, off_t skip2,
                     int sflag, int lflag, int special)
{
    int fd1;
    int rc;

    if (lflag && sflag)
        return errx(ERR_EXIT, "only one of -l and -s may be specified");

    if (!strcmp(file1, "-"))
    {
        if (!strcmp(file2, "-"))
            return errx(ERR_EXIT, "standard input may only be specified once");
        file1 = "stdin";
        fd1 = 1;
        special = 1;
    }
    else
        fd1 = open(file1, O_RDONLY | O_BINARY, 0);
    if (fd1 >= 0)
    {
        rc = cmp_fd_and_file_ex(fd1, file1, skip1,
                                     file2, skip2, sflag, lflag, special);
        close(fd1);
    }
    else
    {
        if (!sflag)
            warn("%s", file1);
        rc = ERR_EXIT;
    }

    return rc;
}


/**
 * Opens and compare two files.
 */
int
cmp_file_and_file(const char *file1, const char *file2, int sflag, int lflag, int special)
{
    return cmp_file_and_file_ex(file1, 0, file2, 0, sflag, lflag, special);
}

