/* Delete entries from a tar archive.

   Copyright (C) 1988, 1992, 1994, 1996, 1997, 2000, 2001, 2003, 2004,
   2005, 2006 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 3, or (at your option) any later
   version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
   Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */

#include <system.h>
#include <system-ioctl.h>

#include "common.h"
#include <rmt.h>
#include "tempname.h"

/* BIG OOPS - THIS DOES NOT CORRECTLY HANDLE EXTENDED HEADERS
   TO BE FIXED ***TBD*** */

#ifdef NEW_DELETE_FUNCTION /* write a NEW achive, without 'deleted' members */

/* externals variables and functions */
extern union block *record_start;
extern union block *record_end;
extern union block *current_block;
extern void add_bytes_written( int len );
extern tarlong get_bytes_written( void );
extern tarlong get_prev_written( void );
extern int name_list_count (void);

/* global variables */
int alwaysround_option = 0;   /* set to 1 to force rounding to record size */
int neverround_option  = 1;   /* set to 1 to NEVER round to record size
                                 just only write 2 null records, and that is it! */
__int64 dbg_roffset = 0;      /* only really for DEBUG */

/* definitions */
#define  WIN_TMP_FILE   "tempnewXXXXXX"   /* note: 6 'X' tail important */

/* local variables */
static int use_env_temp    = 0;  /* put temp file in %TEMP%, but perhaps NOT for DEBUG */
static int do_file_updates = 1;  /* be able to NOT update files, FOR DEBUG */

static char _s_tmp_name[MAX_PATH];
static int new_fd;
static __int64 cumm_written = 0;
static __int64 woffset = 0;
static struct stat _s_sbuf;
static int members_deleted = 0;
static __int64 bytes_deleted = 0;
static char _s_cfm_message[256];

/* functions */

static enum dump_status
sparse_write_file (struct tar_stat_info *st)
{
   FATAL_ERROR ((0, 0, _("sparse write file NOT YET IMPLEMENTED!")));
   return( sparse_skip_file (st) );
}

static void
write_2_new( char * buf, char * msg, int add )
{
   int written;
   int status;

   if( strcmp(msg, "HEADER") == 0 )
   {
        union block *x = (union block *)buf;
        status = tar_checksum(x,true);
        if( status != HEADER_SUCCESS )
        {
           fprintf( stdlis, "WARNING: HEADER does not pass checksum test!" );
        }
   }
   written = write (new_fd, buf, BLOCKSIZE);
   if ( written != BLOCKSIZE )
   {
      FATAL_ERROR ((0, 0, _("Unable to write block to temporary archive!")));
      exit(EXIT_FAILURE);
   }
   if( add )
      add_bytes_written ( written );

   cumm_written += written;
#ifdef DBGV5
   if( verbose_option >= DBGV5 )
   {
      fprintf( stdlis, "v5: Written %s %d bytes from buffer %X, to %d fd (to off %#I64X - %#I64X)\n",
         msg, written, buf, new_fd,
         woffset, (woffset + BLOCKSIZE) );
   }
#endif /* DBGV5 */

#ifdef HEAVY_DEBUG_DELETE
   print_hex( buf, BLOCKSIZE, "out", woffset );
#endif /* HEAVY_DEBUG_DELETE */

   woffset += written;
}

static void
write_skip_file (off_t size)
{
   /* like skip_file(size); */
   union block *x;
   int   skipped = 0;

   mv_size_left (size);
#ifdef DBGV5
   if( verbose_option >= DBGV5 )
   {
      if( size > 0 )
      {
         int blks = size / BLOCKSIZE;
         if( size % BLOCKSIZE )
            blks++;
         fprintf( stdlis, "v5: Writing %d bytes, %d blocks, for %s file.\n",
            size, blks, current_stat_info.file_name );
      }
   }
#endif /* DBGV5 */

   while (size > 0)
   {
      if ( current_block < record_end )
      {
         write_2_new( (char *)current_block, "block", 1 );
      }
      else
      {
         skipped++;
         // fprintf( stdlis, "v5: Current block out of range!\n" );
      }

      x = find_next_block (); /* if (current_block == record_end), do read
                                and current_block set to record_start */
      if (! x)
         FATAL_ERROR ((0, 0, _("Unexpected EOF in archive")));

      if ( current_block == record_start )
      {
         /* write the 'x' we are skipping */
         write_2_new( (char *)current_block, "start", 1 );
         skipped--;
      }
      else if( skipped )
      {
         fprintf( stdlis, "\nv5: CHECK ME: Seems to be sequence error!\n\n" );
         skipped = 0;
      }

      set_next_block_after (x);

      size -= BLOCKSIZE;

      mv_size_left (size);
  }
}

static void write_new (void)
{
   /* this is effectively a skip_member() function,
      except the header, and each block skipped
      is written to the new archive */
   // if ( !current_stat_info.skipped )
   {
      char save_typeflag = current_header->header.typeflag;

      /* 1 - write the HEADER, as is */
      write_2_new( (char *)current_header, "HEADER", 1 );

      set_next_block_after (current_header); /* inc current_block past header, to next */

      mv_begin (&current_stat_info);

      /* 2 - read if required, and write each to the new archive */
      if (current_stat_info.is_sparse)
         sparse_write_file (&current_stat_info);
      else if (save_typeflag != DIRTYPE)
         write_skip_file (current_stat_info.stat.st_size);

      mv_end ();
   }

}

static void
show_current_header( char * msg )
{
   off_t block_ordinal = current_block_ordinal ();
   decode_header (current_header,
      &current_stat_info, &current_format, 0);
   fprintf (stdlis, msg);  /* start output with ... */
   print_header (&current_stat_info, block_ordinal);
}

static void
open_new_archive ( void )
{
   char * new_file = _s_tmp_name;

   *new_file = 0;
   if( use_env_temp )
   {
      char * env = getenv("TEMP");
      size_t len, i;
      int   c;
      if(env)
      {
         strcpy(new_file,env);
         len = strlen(new_file);
         if(len)
         {
            /* should NOT be, but in case there is more than one ... */
            for( i = 0; i < len; i++ )
            {
               c = new_file[i];
               if( c == ';' )
               {
                  new_file[i] = 0;
                  break;
               }
            }
            if(( c != '\\' )&&( c != '/' ))
               strcat(new_file, "\\");
         }
      }
   }
   strcat(new_file, WIN_TMP_FILE);

   if( gen_tempname( new_file, GT_NOCREATE ) == 0 )
   {
#ifdef DBGV5
      if( verbose_option >= DBGV5 )
         fprintf( stdlis, "v5: Creating temp file [%s] ...\n", new_file );
#endif /* DBGV5 */
      new_fd = open (new_file, (O_RDWR | O_CREAT | O_BINARY),
         MODE_RW, rsh_command_option);
      if( new_fd <= 0 )
      {
         FATAL_ERROR ((0, 0, _("Unable to create temporary archive!")));
         exit(EXIT_FAILURE);
      }
   }
   else
   {
      FATAL_ERROR ((0, 0, _("Unable to get temporary file name! Maybe try again?")));
      exit(EXIT_FAILURE);
   }
}

void
win_delete_archive_members (void)
{
   enum read_header logical_status = HEADER_STILL_UNREAD;
   enum read_header previous_status = HEADER_STILL_UNREAD;
   enum read_header status;
   struct name *name;
   char * new_file = _s_tmp_name;
   int   stat_res, is_rounded;
   unsigned int delete_err = 0;
   int results;
   char * pcm = _s_cfm_message;
   union block * saved_headers;
   int saved_count;
   char * cp;

   name_gather ();
   results = name_list_count ();
   if( results == 0 )
   {
      FATAL_ERROR ((0, 0, _("No names given to DELETE!")));
   }

   stat_res = stat( archive_name_array[0], &_s_sbuf );
   is_rounded = 0;
   if( stat_res == -1 )
   {
      /* this would be an indication, that the file does not exist! */
      stat_res = errno;
      if( !stat_res )
         stat_res = ENOENT;  /* assume not found */
   }
   else
   {
      is_rounded = (((_s_sbuf.st_size % record_size ) == 0) ? 1 : 0);
   }

   open_archive (ACCESS_READ); /* this does a lot more than open() ;=)) */

   open_new_archive ();

   saved_headers = xmalloc (record_size);
   saved_count = 0;
   do
   {
      status = read_header (true);
      switch (status)
      {
      case HEADER_STILL_UNREAD:
         FATAL_ERROR ((0, 0, _("INTERNAL ERROR: Unable to read an archive header!")));
         exit(EXIT_FAILURE);
         abort ();
         break;

      case HEADER_SUCCESS:
         if ((name = name_scan (current_stat_info.file_name)) == NULL)
         {
#ifdef DBGV5
            if( verbose_option >= DBGV5 ) show_current_header( "v5: " );
#endif /* DBGV5 */
            write_new (); /* like skip_member (); ... */
            break;
         }
         name->found_count++;
         if (!ISFOUND(name))
         {
#ifdef DBGV5
            if( verbose_option >= DBGV5 ) show_current_header( "v5: " );
#endif /* DBGV5 */
            write_new ();
            break;
         }
         if (interactive_option)
            sprintf(pcm, "delete file, %u bytes, named", current_stat_info.stat.st_size);
         //if (interactive_option && !confirm ("delete", current_stat_info.file_name))
         if (interactive_option && !confirm (pcm, current_stat_info.file_name))
         {
#ifdef DBGV5
            if( verbose_option >= DBGV5 ) show_current_header( "v5: " );
#endif /* DBGV5 */
            write_new ();
            break;
         }
         if( verbose_option )
         {
            if (verbose_option <= 1)
               sprintf( pcm, "delete: %9u bytes: ", current_stat_info.stat.st_size );
            else
               strcpy( pcm, "delete: " );
            show_current_header( pcm );
         }
         /* now NO fall through.  */
         bytes_deleted += current_stat_info.stat.st_size;   /* file DATA size */
         // NO! bytes_deleted += BLOCKSIZE;   /* plus the HEADER structure */
         members_deleted++;
         skip_member ();   /* just skip this guy */
         break;

      case HEADER_SUCCESS_EXTENDED:
         // this would CHANGE the logical status, and exit this top loop
         // ************************************************************
         // logical_status = status;
         // ************************************************************
         // but how to deal with an EXTENDED header
         // =======================================
         saved_headers[saved_count] = *current_header; /* save this EXTENDED header */
         saved_count++;
         while(saved_count)
         {
            status = read_header (false);  /* get NEXT, NOT raw */
            switch(status)
            {
            case HEADER_SUCCESS:
               if ((name = name_scan (current_stat_info.file_name)) == NULL)
               {
                  results = 0;
                  cp = (char *)saved_headers;
                  while(saved_count)
                  {
                     write_2_new( &cp[results], "HEADER", 1 );
                     results += BLOCKSIZE;
                     saved_count--;
                  }
#ifdef DBGV5
                  if( verbose_option >= DBGV5 ) show_current_header( "v5: " );
#endif /* DBGV5 */
                  write_new (); /* like skip_member (); ... */
                  break;
               }
               name->found_count++;
               if (!ISFOUND(name))
               {
                  results = 0;
                  cp = (char *)saved_headers;
                  while(saved_count)
                  {
                     write_2_new( &cp[results], "HEADER", 1 );
                     results += BLOCKSIZE;
                     saved_count--;
                  }
#ifdef DBGV5
                  if( verbose_option >= DBGV5 ) show_current_header( "v5: " );
#endif /* DBGV5 */
                  write_new ();
                  break;
               }
               if (interactive_option)
                  sprintf(pcm, "delete file, %u bytes, named", current_stat_info.stat.st_size);
               //if (interactive_option && !confirm ("delete", current_stat_info.file_name))
               if (interactive_option && !confirm (pcm, current_stat_info.file_name))
               {
                  results = 0;
                  cp = (char *)saved_headers;
                  while(saved_count)
                  {
                     write_2_new( &cp[results], "HEADER", 1 );
                     results += BLOCKSIZE;
                     saved_count--;
                  }
#ifdef DBGV5
                  if( verbose_option >= DBGV5 ) show_current_header( "v5: " );
#endif /* DBGV5 */
                  write_new ();
                  break;
               }
               if( verbose_option )
               {
                  if (verbose_option <= 1)
                     sprintf( pcm, "delete: %9u bytes: ", current_stat_info.stat.st_size );
                  else
                     strcpy( pcm, "delete: " );
                  show_current_header( pcm );
               }
               bytes_deleted += current_stat_info.stat.st_size;   /* file DATA size */
               // NO! bytes_deleted += BLOCKSIZE;   /* plus the HEADER structure */
               members_deleted++;
               skip_member ();   /* just skip this guy */
               saved_count = 0;  /* and discard the saved */
               break;
            case HEADER_SUCCESS_EXTENDED:
               saved_headers[saved_count] = *current_header; /* save this EXTENDED header */
               saved_count++;
               break;
            case HEADER_ZERO_BLOCK:
               if (ignore_zeros_option)
               {
                  set_next_block_after (current_header);
                  break;
               }
               /* Fall through.  */
            case HEADER_END_OF_FILE:
               logical_status = HEADER_END_OF_FILE;
               results = 0;
               cp = (char *)saved_headers;
               while(saved_count)
               {
                  write_2_new( &cp[results], "HEADER", 1 );
                  results += BLOCKSIZE;
                  saved_count--;
               }
               break;
            }
         } /* stay in saved_count loop, until written or cleared(skipped) */
         break;

      case HEADER_ZERO_BLOCK:
         if (ignore_zeros_option)
         {
            set_next_block_after (current_header);
            break;
         }
         /* Fall through.  */
      case HEADER_END_OF_FILE:
         logical_status = HEADER_END_OF_FILE;
         break;

      case HEADER_FAILURE:
         set_next_block_after (current_header);
         switch (previous_status)
         {
         case HEADER_STILL_UNREAD:
            WARN ((0, 0, _("This does not look like a tar archive")));
            /* Fall through.  */
         case HEADER_SUCCESS:
         case HEADER_SUCCESS_EXTENDED:
         case HEADER_ZERO_BLOCK:
            ERROR ((0, 0, _("Skipping to next header")));
            /* Fall through.  */
         case HEADER_FAILURE:
            break;

         case HEADER_END_OF_FILE:
            FATAL_ERROR ((0, 0, _("INTERNAL ERROR: Unable to read an archive header!")));
            exit(EXIT_FAILURE);
            abort ();
            break;
         }
         break;
      }
      previous_status = status;
   }
   while (logical_status == HEADER_STILL_UNREAD);

   /* the original delete.c had LOTS MORE CODE HERE!!!
      *** To BE FIXED *** ***TBD*** */

   if (logical_status == HEADER_END_OF_FILE)
   {
      /* Write the end of archive. */
      memset (record_start->buffer, 0, BLOCKSIZE);
      write_2_new( record_start->buffer, "NULL1", 1 );
      write_2_new( record_start->buffer, "NULL2", 1 );
      /* round it out to record_size, I think? */
      if ( !neverround_option && ( is_rounded || alwaysround_option ) )
      {
         /* get MY cumulative written - true write count,
            as opposed those added to bytes_written,
            which only included file data records */
         __int64 remains = cumm_written % record_size;
         if(remains)
         {
            remains = (record_size - remains) / BLOCKSIZE;
            while(remains--)
               write_2_new( record_start->buffer, "PAD", 1 );
         }
      }
	}

   close ( new_fd );

   close_archive ();

   if( members_deleted )
   {
      char * ps = ((members_deleted == 1) ? "" : "s");
      fprintf( stdlis, "Done: Deleted %d record%s, %I64u bytes (+header%s %d bytes).\n", 
         members_deleted,
         ps,
         bytes_deleted,
         ps,
         (members_deleted * BLOCKSIZE));

      if( do_file_updates )
      {
         /* if success, delete current, and copy new to current */
#ifdef _MSC_VER
         if( !DeleteFile( archive_name_array[0] ) )
         {
            delete_err = GetLastError();
            results = 1;
            if( delete_err == ERROR_ACCESS_DENIED )
            {
               results = _chmod( archive_name_array[0], (_S_IREAD | _S_IWRITE) );
               if( results == 0 )
               {
                  if( !DeleteFile( archive_name_array[0] ) )
                     results = 1;
               }
            }
            if( results )
               WARN ((0, 0, _("Delete of existing archive FAILED, but will attempt COPY!")));
         }
         if( !CopyFile( new_file, archive_name_array[0], 0 ) )
         {
            WARN ((0, 0, _("Copy of NEW file to current archive FAILED")));
         }
         else
         {
            DeleteFile( new_file );
            if( delete_err == ERROR_ACCESS_DENIED )
            {
               /* put back the attribute */
               results = _chmod( archive_name_array[0], _S_IREAD );
            }
         }
#else /* !_MSC_VER */
         WARN ((0, 0, _("Copy, and Delete NOT YET PORTED! But this is intended for WIN32 only!")));
#endif /* _MSC_VER y/n */
      }
   }
   else
   {
      /* if failed, delete new  */
      WARN ((0, 0, _("WARNING: No records DELETED!")));
      if( do_file_updates )
         remove( new_file );  /* remove the TEMPORARY file */
      // or unlink( new_file );  /* will also remove the file in WIN32 */

   }
   free( saved_headers );
   names_notfound ();
}

#endif /* NEW_DELETE_FUNCTION y/n */

#ifdef _MSC_VER
void
win_move_archive (off64_t count)
{
   char * perr = 0;
   int   ertyp = 0;
   off64_t position0 = _lseeki64 (archive, (off64_t) 0, SEEK_CUR);
   off64_t increment = record_size * count;
   off64_t position = position0 + increment;
   off64_t new_pos;
   if (count == 0)
      return;
   if (increment / count != record_size) {
      perr = "Math is screwed!";
      ertyp = 1;
   } else if ((position < position0) != (increment < 0)) {
      if(increment < 0) {
         // if we went backwards then MAYBE compare UNSIGNED positions
         unsigned __int64 lg1 = position;
         unsigned __int64 lg2 = position0;
         if( lg1 < lg2 ) {
            // this is OK
         } else {
            perr = "Position00 screwed!";
            ertyp = 2;
         }
      } else {
       perr = "Position0 screwed!";
       ertyp = 3;
      }
   } else {
      if (position < 0 ) {
         position = 0;
      }
      new_pos = _lseeki64 (archive, position, SEEK_SET);

      if( new_pos != position )
      {
         perr = "SEEK FAILED!";
         ertyp = 4;
      }
   }
   if( perr ) {
      if( ertyp == 2 ) {
         // try it and see
         if(position < 0)
            position = 0;
         new_pos = _lseeki64 (archive, position, SEEK_SET);
         if( new_pos != position )
         {
            perr = "SEEK FAILED!";
            ertyp = 4;
         } else {
            perr = 0;
         }
      }
      if( perr ) {
         seek_error_details (archive_name_array[0], position);
         exit(2);
      }
   }
}

#endif /* _MSC_VER */

/* eof - windelete.c */
