#include<fcntl.h>
#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include<sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <signal.h>


#define FILELENGTH 511  // This is not right
#define DEPTH 1
#define PACKETSIZE 1023

int lsd = 0 ;   // socket for establishing connections
struct backup {     // This is the contents of the header file
    int exists ;
    time_t lastfull ;
    time_t lastinc ;
    time_t latestattempt ;
} ;

void SaveOldBKP( char * , int ) ;
struct backup *PrepareBKP( char * , int ) ;
void backup_directory( int , char * ) ;
void copy( int , char * , int ) ;

void handler( int sig )
{
    if( sig == SIGINT ) {
        printf( "Interrupt caught\n" ) ;
        close( lsd ) ;
        system( "rm ./.tmp*" ) ;
        exit( 0 ) ;
    }
}

unsigned long atoip( char *text )
{
    unsigned long ip ;
    int i , t ;
    i = 0 ;
    ip = t = 0 ;

    while( text[i] != '\0' ) {
        if( text[i] == '.' ) {
            ip = (ip<<8) + t ;
            t = 0 ;
        } else
            t = t*10 + text[i] - '0' ;
        i++ ;
    }
    return (ip<<8) + t ;
}

int start_connection()
{
    struct sockaddr_in *sa ;
    fd_set lsmask ;
    struct timeval timeout ;

    int rc , new_client ;

    if( lsd == 0 ) {    // server not set up
        printf( "Setting up server\n" ) ;
        sa = (struct sockaddr_in *)malloc( sizeof( struct sockaddr_in) ) ;
        sa->sin_family = AF_INET ;
        sa->sin_port = htons(4950) ;
        sa->sin_addr.s_addr = htonl( atoip( "127.0.0.1" ) ) ;
//  try:    http://www.HideYourIPAddress.net       
        lsd = socket( PF_INET , SOCK_STREAM , 0 ) ;
        if( lsd <= 0 ) {
            perror( "Socket not created" ) ;
            kill( getpid() , SIGINT ) ;
        }
        rc = bind( lsd , (struct sockaddr *) sa , sizeof(struct sockaddr_in) ) ;
        if( rc == -1 ) {
            perror( "Bind unsuccessful" ) ;
            kill( getpid() , SIGINT ) ;
        }
        listen( lsd , 5 ) ;
    }
    printf( "checking socket %d\n" , lsd ) ;
    FD_ZERO( &lsmask ) ;
    FD_SET( lsd , &lsmask ) ;
    timeout.tv_sec = 0 ;
    timeout.tv_usec = 10000 ;  // 10 msec
    new_client = select( lsd+1 , &lsmask , NULL , NULL , &timeout ) ;
    if( new_client ) {
        new_client = accept( lsd , NULL , NULL ) ;
        if( new_client == -1 ) {
            perror( "Accept failed unexpectedly" ) ;
            kill( getpid() , SIGINT ) ;
        }
     }
     return new_client ;
}

void handle_connection( int cls )
{
    FILE *PF ;
    int rc ;
    char user[20] , passwd[20] , dir[50] ;
    char PU[20] , PP[20] ;
    char buffer[100] ; 

    rc = recv( cls , buffer , 100 , 0 ) ;
    printf( "Server received %d bytes: (%s)\n" , rc ,buffer ) ;
    buffer[rc] = '\0' ;     // Just in case
    sscanf( buffer , "%s %s\n%s" , user , passwd , dir ) ;
    printf( "user=%s.password=%s.dir=%s\n" , user , passwd , dir ) ;
    PF = fopen( "./.passwdfile" , "rw" ) ;
    if( PF == NULL ) {
        perror( "password file does not exist" ) ;
        PF = fopen( "./.passwdfile" , "w+" ) ;
        if( PF == NULL ) {
            perror( "Failed to create the password file" ) ;
            kill( getpid() , SIGINT ) ;
        } else
            printf( "Password file created\n" ) ;
        fprintf( PF , "%s %s\n" , user , passwd ) ;
    } else {
        fseek( PF , 0 , SEEK_SET ) ;
        for( ; (rc = fscanf( PF , "%s %s\n" , PU , PP )) > 0 ; )
            if( strcmp( PU , user ) == 0 )
                break ;
        if( rc <= 0 ) {
            printf( "New user?\n" ) ;
            fprintf( PF , "%s %s\n" , user , passwd ) ;
        } else if( strcmp( PP , passwd ) != 0 ) {
            printf( "Wrong passwd\n" ) ;
            kill( getpid() , SIGINT ) ;
        } else
            printf( "Existing user\n" ) ;
    }
    backup_directory( cls , dir ) ;
}

// This function thinks it backs up a single directory.
// In fact it can back up any number of files from any number of
// directories.
// It does one thing wrong: it never updates the backup header.
// 
//  Two things must be fixed: the header struct must be sent for each
//  directory (as opposed to once) and it has to be updated for every
//  directory (currently it is not updated at all)
//
void backup_directory( int cls , char *dir )
{
    char buffer[FILELENGTH] , file[FILELENGTH] ; 
    int size , rc ;
//
//  the backup header is retrieved here and it is sent to the client
//  (it is correct only if this is the only directory backed up
//
    struct backup *b = PrepareBKP( dir , DEPTH ) ;
    send( cls , b , sizeof( struct backup ) , 0 ) ;

//  read the file name+size, then the file.
//  send ACKs to inform the client that you got the part sent.
//
    while( (rc = recv( cls , buffer , FILELENGTH , 0 )) > 0 ) {
        buffer[rc] = '\0' ; 
        sscanf( buffer , "%s %d" , file , &size ) ;
        send( cls , "ACK" , 4 , 0 ) ;
        printf( "network transfer of file %s of length %d\n" , file , size ) ;
        copy( cls , file , size ) ;
        send( cls , "ACK" , 4 , 0 ) ;
    }
    close( cls ) ;
}

void copy( int cls , char *file , int size )
{
    int fd ;
    int rc , i , chunk ;
    char *fbuf ;

    chunk = size>PACKETSIZE ? PACKETSIZE : size ;
    fbuf = malloc( chunk + 100 ) ;  // +100 = luck charm

    fd = open( file , O_WRONLY | O_CREAT , S_IRUSR | S_IWUSR ) ;
    if( fd == -1 ) {
        printf( "Failed to open %s\n" , file ) ;
        perror( "open failed" ) ;
        kill( getpid() , SIGINT ) ;
    }
    for( i = 0 ; (rc = recv( cls , fbuf , chunk , 0 )) > 0 ; )  {
        i += rc ;
        write( fd , fbuf , rc ) ;
        if( i >= size ) break ;
    }
    free( fbuf ) ;
    if( size != i )
        printf( "File %s: length mismatch %d\n" , file , size ) ;
    else
        printf( "%s copied successfully\n" , file ) ;
    fflush( stdout ) ;
    close( fd ) ;
}

int main()
{
    int cls ;

    signal( SIGINT , handler ) ;
    for( ; (cls = start_connection()) == 0 ; sleep( 2 ) ) ;
    printf( "Connection established with %d\n" , cls ) ;
    handle_connection( cls ) ;
    close( lsd ) ;
}

//  The function below are the same as for an internal backup. You have
//  no need to look at them, unless compelled by curiosity.
//

void SaveOldBKP( char *dir , int depth )
{
    char nwd[FILELENGTH] ;
    char buf[FILELENGTH] ;
    char tmp[FILELENGTH] ;
    char file[FILELENGTH] ;
    FILE *fp ;
    struct stat filestat ;
    int ret ;

    sprintf( nwd , "%s/.bkp" , dir ) ;
    sprintf( tmp , "ls -s %s" , dir ) ;
    system( tmp ) ;
    ret = mkdir( nwd , S_IRWXU ) ;
    if( depth > 0 && ret < 0 )
        SaveOldBKP( nwd , depth - 1 ) ;
    sprintf( tmp , "%s/.tmp%d" , dir , getpid() ) ;
    sprintf( buf , "ls %s > %s" , dir , tmp ) ;
    system( buf ) ;
    fp = fopen( tmp , "r" ) ;
    while( 1 ) {
        ret = fscanf( fp , "%s" , file ) ;
        if( ret > 0 ) {
            sprintf( buf , "%s/%s" , dir , file ) ;
            if( (ret = lstat( buf , &filestat )) < 0 ) {
                perror( "stat" ) ;
                kill( getpid() , SIGINT ) ;
            } else if( S_ISREG( filestat.st_mode ) ) {
                sprintf( buf , "cp %s/%s %s/%s" , dir , file , nwd , file ) ;
                system( buf ) ;
            }
        } else
            break ;
    }
    unlink( tmp ) ;
}

struct backup *PrepareBKP( char *dir , int depth ) 
{
    int fd ;
    char cwd[FILELENGTH] ;
    char header[FILELENGTH ] ;
    int ret ;

    struct backup *b = (struct backup *) malloc( sizeof( struct backup ) ) ;

    printf( "Backup for directory %s (%d)\n" , dir , depth ) ;
    fflush( stdout ) ;
    if( (fd = open( dir , O_RDONLY , 0 )) == -1 ) {
        printf( "Cannot open directory: %s\n" , dir ) ;
        kill( getpid() , SIGINT ) ;
    }
    close( fd ) ;

    sprintf( cwd , "%s/.bkp" , dir ) ;
    sprintf( header , "%s/header" , cwd ) ;
    ret = mkdir( cwd , S_IRWXU ) ;
    if( ret == 0 ) {
        printf( "No header found\n" ) ;
        fd = open( header , O_CREAT | O_EXCL | O_RDWR , 0600 ) ;
        b->lastinc = 0 ;
        b->lastfull = 0 ;
        b->exists = 0 ;
    } else {
        printf( "directory %s exists?\n" , header ) ;
        fd = open( header , O_RDWR , 0 ) ;
        if( fd < 0 ) {
            perror( "opening bkp/header failed" ) ;
            kill( getpid() , SIGINT ) ;
        }
        ret = read( fd , b , sizeof( struct backup ) ) ;
        if( ret != sizeof( struct backup ) )
            printf( "Length error %d %d\n" , ret , sizeof( struct backup ) ) ;
        if( depth > 0 ) 
            SaveOldBKP( cwd , depth-1 ) ;
        b->exists = 1 ;
    }

    time( &(b->latestattempt) ) ;
    printf( "It is now: %s\n" , asctime( localtime( &(b->latestattempt) ) ) ) ;
    printf( "last backup = %s\n" , asctime( localtime( &(b->lastinc) ) ) ) ;
    if( (ret = lseek( fd , 0 , SEEK_SET )) < 0 )
        perror( "seek" ) ;
    write( fd , b , sizeof( struct backup ) ) ;
    close( fd ) ;
    return b ;
}

