aboutsummaryrefslogblamecommitdiffstats
path: root/autoarchive/autoar-extract.c
blob: e7c4c7e6134bc587863b7418117424c923c717f4 (plain) (tree)



























                                                                             

                          
                        


                          
                    

                                    
                   


                      
 












                                             




                                                                                
                               
                                 
 




                            




                         
 

                     



                            



    









              








                      


                                                         
























































































































































                                                                                




                                        

                                     





                                                                 







                                         

                                      

                        
 







                                                       

   





                               




                                                                  





                                                 
 

                                              






                                                                  




                                                                                    
                                                  








                                                  

                                               










                                                                
                                                   










                                                 

                                              




                                          
                                    






                                                                
                                                      


                   





































































                                                                                 
           
                                                 




                                           
               

                                                    
 




























                                                                                



              
                                                        



                                                           
                                                     

                                                       
 








                         









                    








































                                                                                   
 













































                                                                                  





                                                                
                                                     
        












                                                                           

                                                                  
                                                                                      




                                                                          











                                                                  
                                                       







                                                                                       































































                                                                                      
     

      
 




                                                                       







                                                                                     
                        

 










                                                                  

                                                                       

                                                           
                                                 






                                                                                           
                                                                           









                                                                                                
                                                                           







                                                                                
                                                                                      







                                                                               

                                                                                





                                                                               









                                                                                       









                                                                                         





















































                                                                    












                                                


                           
                  
                            
 

                      



                                                 



                                       

                                       
 

                           


                                              






                                                  
 
 


                                             



                              
                          
                           



                             
                             
                          




                                        

                        

                           
                       
                              
 
                
                        
 
           














                                                     
                                                                        
                                                                               





                                                                          





                           



                                                                        
                                               


                                      






                                                                
                        
                          




























                                                                               


                                                                 




                                           
   
                                                                               
                                                                    



                                              
                                                     

                                    
 
                                                                                           



                                                                                       
                                                            
                                     

                                  
                                                         








                                                           
                                                                            
            
                                



                                                          
                             

                                                        
   







                                                                      


                                                               
                             
                               






                                         
                           
                                       
                         
                        
                                       


                                                               
                                      

           
                                                        
                                                 


                                                               
 

                                                                            
                                                  


                                                                    
                                                                                         


                                                                                  
                                                                                












                                                                                    
                                         
                                                               

                                             









                                                                                



                                                                       
                         
                                                                                        

                             




                                           


                                                               
                                   
                                      



                          


                                                                   
 

                                                       
                                                  

                                      



                                        






                                                                



                                                                 
                                                         


                                                                      


                                                               
                                   
                                      


                          

                                                                            
                                                                    




                                     

                                              


                                                                        




                                                                  
 

                                                                                  
 





                                                                                 
 

                                                                   
 

                                                                                      
 
                                        
 


                                                                      
         
                                     
       

                                                        

     




                                                      
                                             

                                                   

                                         


                                                                 

                                          

                                     
                                        



                             

                                       







                                                                               

                                        







                                                                      


                                                               







                                      

                                 

                                 
                                    


                                       


                                                               




                                                                         


                                                                
                                           
                                                                    
                                           



                                                                      

                                                                     








                                               







                                                             
                             





                                                     

              
                           

                                                  
                                          
                                                                 
 
/* vim: set sw=2 ts=2 sts=2 et: */
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * autoar-extract.c
 * Automatically extract archives in some GNOME programs
 *
 * Copyright (C) 2013  Ting-Wei Lan
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 */

#include "config.h"

#include "autoar-extract.h"

#include "autoar-common.h"
#include "autoar-pref.h"

#include <archive.h>
#include <archive_entry.h>
#include <gio/gio.h>
#include <gobject/gvaluecollector.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#if defined HAVE_MKFIFO || defined HAVE_MKNOD
# include <fcntl.h>
#endif

#ifdef HAVE_GETPWNAM
# include <pwd.h>
#endif

#ifdef HAVE_GETGRNAM
# include <grp.h>
#endif


G_DEFINE_TYPE (AutoarExtract, autoar_extract, G_TYPE_OBJECT)

#define AUTOAR_EXTRACT_GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), AUTOAR_TYPE_EXTRACT, AutoarExtractPrivate))

#define BUFFER_SIZE (64 * 1024)
#define NOT_AN_ARCHIVE_ERRNO 2013

struct _AutoarExtractPrivate
{
  char *source;
  char *output;

  guint64 size;
  guint64 completed_size;

  guint files;
  guint completed_files;

  AutoarPref *arpref;

  GInputStream *istream;
  void         *buffer;
  gssize        buffer_size;
  GError       *error;
};

enum
{
  SCANNED,
  DECIDE_DEST,
  PROGRESS,
  COMPLETED,
  ERROR,
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_SOURCE,
  PROP_OUTPUT,
  PROP_SIZE,
  PROP_COMPLETED_SIZE,
  PROP_FILES,
  PROP_COMPLETED_FILES
};

static guint autoar_extract_signals[LAST_SIGNAL] = { 0 };
static GQuark autoar_extract_quark;

static void
autoar_extract_get_property (GObject    *object,
                             guint       property_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  AutoarExtract *arextract;
  AutoarExtractPrivate *priv;

  arextract = AUTOAR_EXTRACT (object);
  priv = arextract->priv;

  switch (property_id) {
    case PROP_SOURCE:
      g_value_set_string (value, priv->source);
      break;
    case PROP_OUTPUT:
      g_value_set_string (value, priv->output);
      break;
    case PROP_SIZE:
      g_value_set_uint64 (value, priv->size);
      break;
    case PROP_COMPLETED_SIZE:
      g_value_set_uint64 (value, priv->completed_size);
      break;
    case PROP_FILES:
      g_value_set_uint (value, priv->files);
      break;
    case PROP_COMPLETED_FILES:
      g_value_set_uint (value, priv->completed_files);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
autoar_extract_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  AutoarExtract *arextract;
  AutoarExtractPrivate *priv;

  arextract = AUTOAR_EXTRACT (object);
  priv = arextract->priv;

  switch (property_id) {
    case PROP_SIZE:
      autoar_extract_set_size (arextract, g_value_get_uint64 (value));
      break;
    case PROP_COMPLETED_SIZE:
      autoar_extract_set_completed_size (arextract, g_value_get_uint64 (value));
      break;
    case PROP_FILES:
      autoar_extract_set_files (arextract, g_value_get_uint (value));
      break;
    case PROP_COMPLETED_FILES:
      autoar_extract_set_completed_files (arextract, g_value_get_uint (value));
      break;
    case PROP_SOURCE:
      g_free (priv->source);
      priv->source = g_value_dup_string (value);
      break;
    case PROP_OUTPUT:
      g_free (priv->output);
      priv->output = g_value_dup_string (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

char*
autoar_extract_get_source (AutoarExtract *arextract)
{
  g_return_val_if_fail (AUTOAR_IS_EXTRACT (arextract), NULL);
  return arextract->priv->source;
}

char*
autoar_extract_get_output (AutoarExtract *arextract)
{
  g_return_val_if_fail (AUTOAR_IS_EXTRACT (arextract), NULL);
  return arextract->priv->output;
}

guint64
autoar_extract_get_size (AutoarExtract *arextract)
{
  g_return_val_if_fail (AUTOAR_IS_EXTRACT (arextract), 0);
  return arextract->priv->size;
}

guint64
autoar_extract_get_completed_size (AutoarExtract *arextract)
{
  g_return_val_if_fail (AUTOAR_IS_EXTRACT (arextract), 0);
  return arextract->priv->completed_size;
}

guint
autoar_extract_get_files (AutoarExtract *arextract)
{
  g_return_val_if_fail (AUTOAR_IS_EXTRACT (arextract), 0);
  return arextract->priv->files;
}

guint
autoar_extract_get_completed_files (AutoarExtract *arextract)
{
  g_return_val_if_fail (AUTOAR_IS_EXTRACT (arextract), 0);
  return arextract->priv->completed_files;
}

void
autoar_extract_set_size (AutoarExtract *arextract,
                         guint64 size)
{
  g_return_if_fail (AUTOAR_IS_EXTRACT (arextract));
  arextract->priv->size = size;
}

void
autoar_extract_set_completed_size (AutoarExtract *arextract,
                                   guint64 completed_size)
{
  g_return_if_fail (AUTOAR_IS_EXTRACT (arextract));
  g_return_if_fail (completed_size <= arextract->priv->completed_size);
  arextract->priv->completed_size = completed_size;
}

void
autoar_extract_set_files (AutoarExtract *arextract,
                          guint files)
{
  g_return_if_fail (AUTOAR_IS_EXTRACT (arextract));
  arextract->priv->files = files;
}

void
autoar_extract_set_completed_files (AutoarExtract *arextract,
                                    guint completed_files)
{
  g_return_if_fail (AUTOAR_IS_EXTRACT (arextract));
  g_return_if_fail (completed_files <= arextract->priv->completed_files);
  arextract->priv->completed_files = completed_files;
}

static void
autoar_extract_dispose (GObject *object)
{
  AutoarExtract *arextract;
  arextract = AUTOAR_EXTRACT (object);

  g_debug ("AutoarExtract: dispose");

  g_clear_object (&(arextract->priv->arpref));

  G_OBJECT_CLASS (autoar_extract_parent_class)->dispose (object);
}

static void
autoar_extract_finalize (GObject *object)
{
  AutoarExtract *arextract;
  AutoarExtractPrivate *priv;

  arextract = AUTOAR_EXTRACT (object);
  priv = arextract->priv;

  g_debug ("AutoarExtract: finalize");

  g_free (priv->source);
  priv->source = NULL;

  g_free (priv->output);
  priv->output = NULL;

  if (priv->istream != NULL) {
    if (!g_input_stream_is_closed (priv->istream)) {
      g_input_stream_close (priv->istream, NULL, NULL);
    }
    g_object_unref (priv->istream);
  }

  g_free (priv->buffer);
  priv->buffer = NULL;

  if (priv->error != NULL) {
    g_error_free (priv->error);
    priv->error = NULL;
  }

  G_OBJECT_CLASS (autoar_extract_parent_class)->finalize (object);
}

static int
libarchive_read_open_cb (struct archive *ar_read,
                         void *client_data)
{
  AutoarExtract *arextract;
  GFile *file;

  g_debug ("libarchive_read_open_cb: called");

  arextract = (AutoarExtract*)client_data;
  if (arextract->priv->error != NULL) {
    return ARCHIVE_FATAL;
  }

  file = g_file_new_for_commandline_arg (arextract->priv->source);

  arextract->priv->istream = (GInputStream*)g_file_read (file,
                                                         NULL,
                                                         &(arextract->priv->error));
  g_return_val_if_fail (arextract->priv->error == NULL, ARCHIVE_FATAL);

  g_debug ("libarchive_read_open_cb: ARCHIVE_OK");
  return ARCHIVE_OK;
}

static int
libarchive_read_close_cb (struct archive *ar_read,
                          void *client_data)
{
  AutoarExtract *arextract;

  g_debug ("libarchive_read_close_cb: called");

  arextract = (AutoarExtract*)client_data;
  if (arextract->priv->error != NULL) {
    return ARCHIVE_FATAL;
  }

  if (arextract->priv->istream != NULL) {
    g_input_stream_close (arextract->priv->istream, NULL, NULL);
    g_object_unref (arextract->priv->istream);
    arextract->priv->istream = NULL;
  }

  g_debug ("libarchive_read_close_cb: ARCHIVE_OK");
  return ARCHIVE_OK;
}

static ssize_t
libarchive_read_read_cb (struct archive *ar_read,
                         void *client_data,
                         const void **buffer)
{
  AutoarExtract *arextract;
  gssize read_size;

  g_debug ("libarchive_read_read_cb: called");

  arextract = (AutoarExtract*)client_data;
  if (arextract->priv->error != NULL) {
    return -1;
  }

  *buffer = arextract->priv->buffer;
  read_size = g_input_stream_read (arextract->priv->istream,
                                   arextract->priv->buffer,
                                   arextract->priv->buffer_size,
                                   NULL,
                                   &(arextract->priv->error));
  g_return_val_if_fail (arextract->priv->error == NULL, -1);

  g_debug ("libarchive_read_read_cb: %lu", read_size);
  return read_size;
}

static off_t
libarchive_read_seek_cb (struct archive *ar_read,
                         void *client_data,
                         off_t request,
                         int whence)
{
  AutoarExtract *arextract;
  GSeekable *seekable;
  GSeekType  seektype;
  off_t new_offset;

  g_debug ("libarchive_read_seek_cb: called");

  arextract = (AutoarExtract*)client_data;
  seekable = (GSeekable*)(arextract->priv->istream);
  if (arextract->priv->error != NULL) {
    return -1;
  }

  switch (whence) {
    case SEEK_SET:
      seektype = G_SEEK_SET;
      break;
    case SEEK_CUR:
      seektype = G_SEEK_CUR;
      break;
    case SEEK_END:
      seektype = G_SEEK_END;
      break;
    default:
      return -1;
  }

  g_seekable_seek (seekable,
                   request,
                   seektype,
                   NULL,
                   &(arextract->priv->error));
  new_offset = g_seekable_tell (seekable);
  g_return_val_if_fail (arextract->priv->error == NULL, -1);

  g_debug ("libarchive_read_seek_cb: %"G_GOFFSET_FORMAT, (goffset)new_offset);
  return new_offset;
}

static off_t
libarchive_read_skip_cb (struct archive *ar_read,
                         void *client_data,
                         off_t request)
{
  AutoarExtract *arextract;
  GSeekable *seekable;
  off_t old_offset, new_offset;

  g_debug ("libarchive_read_skip_cb: called");

  arextract = (AutoarExtract*)client_data;
  seekable = (GSeekable*)(arextract->priv->istream);
  if (arextract->priv->error != NULL) {
    return -1;
  }

  old_offset = g_seekable_tell (seekable);
  new_offset = libarchive_read_seek_cb (ar_read, client_data, request, SEEK_CUR);
  if (new_offset > old_offset)
    return (new_offset - old_offset);

  return 0;
}

static void
g_pattern_spec_free_safe (void *pattern_compiled)
{
  if (pattern_compiled != NULL)
    g_pattern_spec_free (pattern_compiled);
}

static gboolean
autoar_extract_do_pattern_check (const char *path,
                                 GPtrArray *pattern)
{
  char **path_components;
  GArray *path_components_len;

  int i, j, len;

  path_components = g_strsplit (path, "/", G_MAXINT);
  path_components_len = g_array_new (FALSE, FALSE, sizeof(size_t));
  for (i = 0; path_components[i] != NULL; i++) {
    len = strlen (path_components[i]);
    g_array_append_val (path_components_len, len);
  }

  for (i = 0; g_ptr_array_index (pattern, i) != NULL; i++) {
    for (j = 0; path_components[j] != NULL; j++) {
      if (g_pattern_match (g_ptr_array_index (pattern, i),
                           g_array_index (path_components_len, size_t, j),
                           path_components[j],
                           NULL)) {
        g_debug ("autoar_extract_do_pattern_check: ### %s", path_components[j]);
        g_strfreev (path_components);
        g_array_unref (path_components_len);
        return FALSE;
      }
    }
  }

  g_strfreev (path_components);
  g_array_unref (path_components_len);

  return TRUE;
}

static void
autoar_extract_do_write_entry (AutoarExtract *arextract,
                               struct archive *a,
                               struct archive_entry *entry,
                               GFile *dest,
                               GHashTable *userhash,
                               GHashTable *grouphash,
                               gboolean in_thread,
                               gboolean use_raw_format)
{
  GOutputStream *ostream;
  GFileInfo *info;
  GFile *parent;
  mode_t filetype;
  const void *buffer;
  size_t size, written;
  off_t offset;
  int r;

#ifdef HAVE_GETPWNAM
  const char *uname;
#endif

#ifdef HAVE_GETGRNAM
  const char *gname;
#endif

  guint32 uid, gid;

  parent = g_file_get_parent (dest);
  if (!g_file_query_exists (parent, NULL))
    g_file_make_directory_with_parents (parent, NULL, NULL);
  g_object_unref (parent);

  info = g_file_info_new ();

  /* time */
  g_debug ("autoar_extract_do_write_entry: time");
  if (archive_entry_atime_is_set (entry)) {
    g_file_info_set_attribute_uint64 (info,
                                      G_FILE_ATTRIBUTE_TIME_ACCESS,
                                      archive_entry_atime (entry));
    g_file_info_set_attribute_uint32 (info,
                                      G_FILE_ATTRIBUTE_TIME_ACCESS_USEC,
                                      archive_entry_atime_nsec (entry) / 1000);
  }
  if (archive_entry_birthtime_is_set (entry)) {
    g_file_info_set_attribute_uint64 (info,
                                      G_FILE_ATTRIBUTE_TIME_CREATED,
                                      archive_entry_birthtime (entry));
    g_file_info_set_attribute_uint32 (info,
                                      G_FILE_ATTRIBUTE_TIME_CREATED_USEC,
                                      archive_entry_birthtime_nsec (entry) / 1000);
  }
  if (archive_entry_ctime_is_set (entry)) {
    g_file_info_set_attribute_uint64 (info,
                                      G_FILE_ATTRIBUTE_TIME_CHANGED,
                                      archive_entry_ctime (entry));
    g_file_info_set_attribute_uint32 (info,
                                      G_FILE_ATTRIBUTE_TIME_CHANGED_USEC,
                                      archive_entry_ctime_nsec (entry) / 1000);
  }
  if (archive_entry_mtime_is_set (entry)) {
    g_file_info_set_attribute_uint64 (info,
                                      G_FILE_ATTRIBUTE_TIME_MODIFIED,
                                      archive_entry_mtime (entry));
    g_file_info_set_attribute_uint32 (info,
                                      G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
                                      archive_entry_mtime_nsec (entry) / 1000);
  }

  /* user */
  g_debug ("autoar_extract_do_write_entry: user");
#ifdef HAVE_GETPWNAM
  if ((uname = archive_entry_uname (entry)) != NULL) {
    void *got_uid;
    if (g_hash_table_lookup_extended (userhash, uname, NULL, &got_uid) == TRUE) {
      uid = GPOINTER_TO_UINT (got_uid);
    } else {
      struct passwd *pwd = getpwnam (uname);
      if (pwd == NULL) {
        uid = archive_entry_uid (entry);
      } else {
        uid = pwd->pw_uid;
        g_hash_table_insert (userhash, g_strdup (uname), GUINT_TO_POINTER (uid));
      }
    }
    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, uid);
  } else
#endif
  if ((uid = archive_entry_uid (entry)) != 0) {
    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, uid);
  }

  /* group */
  g_debug ("autoar_extract_do_write_entry: group");
#ifdef HAVE_GETGRNAM
  if ((gname = archive_entry_gname (entry)) != NULL) {
    void *got_gid;
    if (g_hash_table_lookup_extended (grouphash, gname, NULL, &got_gid) == TRUE) {
      gid = GPOINTER_TO_UINT (got_gid);
    } else {
      struct group *grp = getgrnam (gname);
      if (grp == NULL) {
        gid = archive_entry_gid (entry);
      } else {
        gid = grp->gr_gid;
        g_hash_table_insert (grouphash, g_strdup (gname), GUINT_TO_POINTER (gid));
      }
    }
    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, gid);
  } else
#endif
  if ((gid = archive_entry_gid (entry)) != 0) {
    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, gid);
  }

  /* permissions */
  g_debug ("autoar_extract_do_write_entry: permissions");
  g_file_info_set_attribute_uint32 (info,
                                    G_FILE_ATTRIBUTE_UNIX_MODE,
                                    archive_entry_mode (entry));

  g_debug ("autoar_extract_do_write_entry: writing");
  r = 0;
  switch (filetype = archive_entry_filetype (entry)) {
    case AE_IFREG:
      ostream = (GOutputStream*)g_file_replace (dest,
                                                NULL,
                                                FALSE,
                                                G_FILE_CREATE_NONE,
                                                NULL,
                                                &(arextract->priv->error));
      if (arextract->priv->error != NULL) {
        g_object_unref (info);
        return;
      }
      if (ostream != NULL) {
        /* Archive entry size may be zero if we use raw format. */
        if (archive_entry_size(entry) > 0 || use_raw_format) {
          while (archive_read_data_block (a, &buffer, &size, &offset) == ARCHIVE_OK) {
            /* buffer == NULL occurs in some zip archives when an entry is
             * completely read. We just skip this situation to prevent GIO
             * warnings. */
            if (buffer == NULL)
              continue;
            g_output_stream_write_all (ostream,
                                       buffer,
                                       size,
                                       &written,
                                       NULL,
                                       &(arextract->priv->error));
            if (arextract->priv->error != NULL) {
              g_output_stream_close (ostream, NULL, NULL);
              g_object_unref (ostream);
              g_object_unref (info);
              return;
            }
            arextract->priv->completed_size += written;
            autoar_common_g_signal_emit (in_thread,
                                         arextract,
                                         autoar_extract_signals[PROGRESS],
                                         0,
                                         ((double)(arextract->priv->completed_size)) /
                                         ((double)(arextract->priv->size)),
                                         ((double)(arextract->priv->completed_files)) /
                                         ((double)(arextract->priv->files)));
          }
        }
        g_output_stream_close (ostream, NULL, NULL);
        g_object_unref (ostream);
      }
      break;
    case AE_IFDIR:
      g_file_make_directory_with_parents (dest, NULL, &(arextract->priv->error));
      if (arextract->priv->error != NULL) {
        /* "File exists" is not a fatal error */
        if (arextract->priv->error->code == G_IO_ERROR_EXISTS) {
          g_error_free (arextract->priv->error);
          arextract->priv->error = NULL;
        }
      }
      break;
    case AE_IFLNK:
      g_file_make_symbolic_link (dest,
                                 archive_entry_symlink (entry),
                                 NULL,
                                 &(arextract->priv->error));
      break;
    /* FIFOs, sockets, block files, character files are not important
     * in the regular archives, so errors are not fatal. */
#if defined HAVE_MKFIFO || defined HAVE_MKNOD
    case AE_IFIFO:
# ifdef HAVE_MKFIFO
      r = mkfifo (g_file_get_path (dest), archive_entry_mode (entry));
# else
      r = mknod (g_file_get_path (dest),
                 S_IFIFO | archive_entry_mode (entry),
                 0);
# endif
      break;
#endif
#ifdef HAVE_MKNOD
    case AE_IFSOCK:
      r = mknod (g_file_get_path (dest),
                 S_IFSOCK | archive_entry_mode (entry),
                 0);
      break;
    case AE_IFBLK:
      r = mknod (g_file_get_path (dest),
                 S_IFBLK | archive_entry_mode (entry),
                 archive_entry_rdev (entry));
      break;
    case AE_IFCHR:
      r = mknod (g_file_get_path (dest),
                 S_IFCHR | archive_entry_mode (entry),
                 archive_entry_rdev (entry));
      break;
#endif
  }

#if defined HAVE_MKFIFO || defined HAVE_MKNOD
  /* Create a empty regular file if we cannot create the special file. */
  if (r < 0 && (filetype == AE_IFIFO ||
                filetype == AE_IFSOCK ||
                filetype == AE_IFBLK ||
                filetype == AE_IFCHR)) {
    ostream = (GOutputStream*)g_file_append_to (dest, G_FILE_CREATE_NONE, NULL, NULL);
    if (ostream != NULL) {
      g_output_stream_close (ostream, NULL, NULL);
      g_object_unref (ostream);
    }
  }
#endif

  g_debug ("autoar_extract_do_write_entry: applying info");
  g_file_set_attributes_from_info (dest,
                                   info,
                                   G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                   NULL,
                                   &(arextract->priv->error));

  if (arextract->priv->error != NULL) {
    g_debug ("autoar_extract_do_write_entry: %s\n", arextract->priv->error->message);
    g_error_free (arextract->priv->error);
    arextract->priv->error = NULL;
  }

  g_object_unref (info);
}

static void
autoar_extract_class_init (AutoarExtractClass *klass)
{
  GObjectClass *object_class;
  GType type;

  object_class = G_OBJECT_CLASS (klass);
  type = G_TYPE_FROM_CLASS (klass);

  g_type_class_add_private (klass, sizeof (AutoarExtractPrivate));

  autoar_extract_quark = g_quark_from_static_string ("autoar-extract");

  object_class->get_property = autoar_extract_get_property;
  object_class->set_property = autoar_extract_set_property;
  object_class->dispose = autoar_extract_dispose;
  object_class->finalize = autoar_extract_finalize;

  g_object_class_install_property (object_class, PROP_SOURCE,
                                   g_param_spec_string ("source",
                                                        "Source archive",
                                                        "The archive file to be extracted",
                                                        NULL,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_NAME |
                                                        G_PARAM_STATIC_NICK |
                                                        G_PARAM_STATIC_BLURB));

  g_object_class_install_property (object_class, PROP_OUTPUT,
                                   g_param_spec_string ("output",
                                                        "Output directory",
                                                        "Output directory of extracted archive",
                                                        NULL,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_NAME |
                                                        G_PARAM_STATIC_NICK |
                                                        G_PARAM_STATIC_BLURB));

  g_object_class_install_property (object_class, PROP_SIZE,
                                   g_param_spec_uint64 ("size",
                                                        "File size",
                                                        "Size of the extracted files",
                                                        0, G_MAXUINT64, 0,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_STATIC_NAME |
                                                        G_PARAM_STATIC_NICK |
                                                        G_PARAM_STATIC_BLURB));

  g_object_class_install_property (object_class, PROP_COMPLETED_SIZE,
                                   g_param_spec_uint64 ("completed-size",
                                                        "Written file size",
                                                        "Bytes written to disk",
                                                        0, G_MAXUINT64, 0,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_STATIC_NAME |
                                                        G_PARAM_STATIC_NICK |
                                                        G_PARAM_STATIC_BLURB));

  g_object_class_install_property (object_class, PROP_FILES,
                                   g_param_spec_uint ("files",
                                                      "Files",
                                                      "Number of files in the archive",
                                                      0, G_MAXUINT32, 0,
                                                      G_PARAM_READWRITE |
                                                      G_PARAM_STATIC_NAME |
                                                      G_PARAM_STATIC_NICK |
                                                      G_PARAM_STATIC_BLURB));

  g_object_class_install_property (object_class, PROP_COMPLETED_FILES,
                                   g_param_spec_uint ("completed-files",
                                                      "Written files",
                                                      "Number of files has been written",
                                                      0, G_MAXUINT32, 0,
                                                      G_PARAM_READWRITE |
                                                      G_PARAM_STATIC_NAME |
                                                      G_PARAM_STATIC_NICK |
                                                      G_PARAM_STATIC_BLURB));

  autoar_extract_signals[SCANNED] =
    g_signal_new ("scanned",
                  type,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (AutoarExtractClass, scanned),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__UINT,
                  G_TYPE_NONE,
                  1,
                  G_TYPE_UINT);

  autoar_extract_signals[DECIDE_DEST] =
    g_signal_new ("decide-dest",
                  type,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (AutoarExtractClass, decide_dest),
                  NULL, NULL,
                  g_cclosure_marshal_generic,
                  G_TYPE_NONE,
                  1,
                  G_TYPE_FILE);

  autoar_extract_signals[PROGRESS] =
    g_signal_new ("progress",
                  type,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (AutoarExtractClass, progress),
                  NULL, NULL,
                  g_cclosure_marshal_generic,
                  G_TYPE_NONE,
                  2,
                  G_TYPE_DOUBLE,
                  G_TYPE_DOUBLE);

  autoar_extract_signals[COMPLETED] =
    g_signal_new ("completed",
                  type,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (AutoarExtractClass, completed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE,
                  0);

  autoar_extract_signals[ERROR] =
    g_signal_new ("error",
                  type,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (AutoarExtractClass, error),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__POINTER,
                  G_TYPE_NONE,
                  1,
                  G_TYPE_POINTER);
}

static void
autoar_extract_init (AutoarExtract *arextract)
{
  AutoarExtractPrivate *priv;

  priv = AUTOAR_EXTRACT_GET_PRIVATE (arextract);
  arextract->priv = priv;

  priv->source = NULL;
  priv->output = NULL;

  priv->size = 0;
  priv->completed_size = 0;

  priv->files = 0;
  priv->completed_files = 0;

  priv->arpref = NULL;

  priv->istream = NULL;
  priv->buffer_size = BUFFER_SIZE;
  priv->buffer = g_new (char, priv->buffer_size);
  priv->error = NULL;
}

AutoarExtract*
autoar_extract_new (const char *source,
                    const char *output,
                    AutoarPref *arpref)
{
  AutoarExtract* arextract;

  g_return_val_if_fail (source != NULL, NULL);
  g_return_val_if_fail (output != NULL, NULL);

  arextract = g_object_new (AUTOAR_TYPE_EXTRACT,
                            "source", source,
                            "output", output,
                            NULL);
  arextract->priv->arpref = g_object_ref (arpref);

  return arextract;
}

static void
autoar_extract_run (AutoarExtract *arextract,
                    gboolean in_thread)
{
  struct archive *a;
  struct archive_entry *entry;

  char *pathname_basename;
  char *pathname_extension;
  char *pathname_prefix;
  int pathname_prefix_len;

  gboolean has_top_level_dir;
  gboolean has_only_one_file;
  gboolean use_raw_format;
  char *top_level_dir_basename;
  char *top_level_dir_basename_modified;
  GFile *top_level_parent_dir;
  GFile *top_level_dir;

  GHashTable *userhash;
  GHashTable *grouphash;
  GHashTable *bad_filename;

  const char **pattern;
  GPtrArray *pattern_compiled;

  GFile *source;
  char *source_basename;

  int i, r;

  g_return_if_fail (AUTOAR_IS_EXTRACT (arextract));
  g_return_if_fail (arextract->priv->source != NULL);
  g_return_if_fail (arextract->priv->output != NULL);

  a = archive_read_new ();
  archive_read_support_filter_all (a);
  archive_read_support_format_all (a);

  /* Reset all counter variables */
  arextract->priv->size = 0;
  arextract->priv->completed_size = 0;
  arextract->priv->files = 0;
  arextract->priv->completed_files = 0;

  pattern = autoar_pref_get_pattern_to_ignore (arextract->priv->arpref);
  pattern_compiled = g_ptr_array_new_with_free_func (g_pattern_spec_free_safe);
  if (pattern != NULL) {
    for (i = 0; pattern[i] != NULL; i++)
      g_ptr_array_add (pattern_compiled, g_pattern_spec_new (pattern[i]));
  }
  g_ptr_array_add (pattern_compiled, NULL);

  pathname_prefix = NULL;
  pathname_prefix_len = 0;
  has_top_level_dir = TRUE;
  has_only_one_file = TRUE;
  use_raw_format = FALSE;

  /* Step 1: Scan all file names in the archive
   * We have to check whether the archive contains a top-level directory
   * before performing the extraction. We emit the "scanned" signal when
   * the checking is completed. */
  g_debug ("autoar_extract_run: Step 1, Scan");
  a = archive_read_new ();
  archive_read_support_filter_all (a);
  archive_read_support_format_all (a);
  archive_read_set_open_callback (a, libarchive_read_open_cb);
  archive_read_set_read_callback (a, libarchive_read_read_cb);
  archive_read_set_close_callback (a, libarchive_read_close_cb);
  archive_read_set_seek_callback (a, libarchive_read_seek_cb);
  archive_read_set_skip_callback (a, libarchive_read_skip_cb);
  archive_read_set_callback_data (a, arextract);
  r = archive_read_open1 (a);
  if (r != ARCHIVE_OK) {
    archive_read_free (a);
    a = archive_read_new ();
    archive_read_support_filter_all (a);
    archive_read_support_format_raw (a);
    archive_read_set_open_callback (a, libarchive_read_open_cb);
    archive_read_set_read_callback (a, libarchive_read_read_cb);
    archive_read_set_close_callback (a, libarchive_read_close_cb);
    archive_read_set_seek_callback (a, libarchive_read_seek_cb);
    archive_read_set_skip_callback (a, libarchive_read_skip_cb);
    archive_read_set_callback_data (a, arextract);
    r = archive_read_open1 (a);
    if (r != ARCHIVE_OK || archive_filter_count (a) <= 1) {
      if (arextract->priv->error == NULL) {
        if (r != ARCHIVE_OK) {
          arextract->priv->error = g_error_new (autoar_extract_quark,
                                                archive_errno (a),
                                                "\'%s\': %s",
                                                arextract->priv->source,
                                                archive_error_string (a));
        } else {
          /* If we only use raw format and filter count is one, libarchive will
           * not do anything except for just copying the source file. We do not
           * want this thing to happen because it does unnecesssary copying. */
          arextract->priv->error = g_error_new (autoar_extract_quark,
                                                NOT_AN_ARCHIVE_ERRNO,
                                                "\'%s\': %s",
                                                arextract->priv->source,
                                                "not an archive");
        }
      }
      autoar_common_g_signal_emit (in_thread, arextract,
                                   autoar_extract_signals[ERROR],
                                   0, arextract->priv->error);
      archive_read_free (a);
      g_ptr_array_unref (pattern_compiled);
      return;
    }
    use_raw_format = TRUE;
  }
  bad_filename = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  while ((r = archive_read_next_header (a, &entry)) == ARCHIVE_OK) {
    const char *pathname, *dir_sep_location;
    size_t skip_len, prefix_len;

    pathname = archive_entry_pathname (entry);
    g_debug ("autoar_extract_run: %d: pathname = %s",
             arextract->priv->files,
             pathname);

    if (!use_raw_format && !autoar_extract_do_pattern_check (pathname, pattern_compiled)) {
      g_hash_table_insert (bad_filename, g_strdup (pathname), GUINT_TO_POINTER (TRUE));
      continue;
    }

    g_debug ("autoar_extract_run: %d: pattern check passed",
             arextract->priv->files);

    if (pathname_prefix == NULL) {
      pathname_basename = g_path_get_basename (pathname);
      skip_len = strspn (pathname, "./");
      dir_sep_location = strchr (pathname + skip_len, '/');
      if (dir_sep_location == NULL) {
        prefix_len = strlen (pathname);
      } else {
        prefix_len = dir_sep_location - pathname;
      }
      pathname_prefix = g_strndup (pathname, prefix_len);
      pathname_prefix_len = prefix_len;
      g_debug ("autoar_extract_run: pathname_prefix = %s", pathname_prefix);
    } else {
      has_only_one_file = FALSE;
      if (!g_str_has_prefix (pathname, pathname_prefix)) {
        has_top_level_dir = FALSE;
      }
    }
    arextract->priv->files++;
    arextract->priv->size += archive_entry_size (entry);
    archive_read_data_skip (a);
  }
  if (r != ARCHIVE_EOF) {
    if (arextract->priv->error == NULL) {
      arextract->priv->error = g_error_new (autoar_extract_quark,
                                            archive_errno (a),
                                            "\'%s\': %s",
                                            arextract->priv->source,
                                            archive_error_string (a));
    }
    autoar_common_g_signal_emit (in_thread, arextract,
                                 autoar_extract_signals[ERROR],
                                 0, arextract->priv->error);
    g_free (pathname_prefix);
    g_free (pathname_basename);
    g_ptr_array_unref (pattern_compiled);
    g_hash_table_unref (bad_filename);
    archive_read_close (a);
    archive_read_free (a);
    return;
  }

  g_free (pathname_prefix);
  g_ptr_array_unref (pattern_compiled);
  archive_read_close (a);
  archive_read_free (a);
  if (arextract->priv->error != NULL) {
    autoar_common_g_signal_emit (in_thread, arextract,
                                 autoar_extract_signals[ERROR],
                                 0, arextract->priv->error);
    g_hash_table_unref (bad_filename);
    return;
  }
  g_debug ("autoar_extract_run: has_top_level_dir = %s",
           has_top_level_dir ? "TRUE" : "FALSE");
  autoar_common_g_signal_emit (in_thread, arextract,
                               autoar_extract_signals[SCANNED],
                               0, arextract->priv->files);

  /* Step 2: Create necessary directories
   * If the archive contains only one file, we don't create the directory */
  g_debug ("autoar_extract_run: Step 2, Mkdir-p");
  source = g_file_new_for_commandline_arg (arextract->priv->source);
  source_basename = g_file_get_basename (source);
  g_object_unref (source);
  top_level_dir_basename = autoar_common_get_basename_remove_extension (source_basename);
  top_level_parent_dir = g_file_new_for_commandline_arg (arextract->priv->output);
  top_level_dir = g_file_get_child (top_level_parent_dir, top_level_dir_basename);

  pathname_extension = autoar_common_get_filename_extension (pathname_basename);
  if (has_only_one_file && (pathname_extension != pathname_basename)) {
    /* If we only have one file, we have to add the file extension.
     * Although we use the variable `top_level_dir', it may be a regular
     * file, so the extension is important. */
    char *new_filename;
    new_filename = g_strconcat (top_level_dir_basename, pathname_extension, NULL);
    top_level_dir = g_file_get_child (top_level_parent_dir, new_filename);
    g_free (new_filename);
  } else {
    top_level_dir = g_file_get_child (top_level_parent_dir, top_level_dir_basename);
    pathname_extension = "";
  }

  top_level_dir_basename_modified = NULL;
  for (i = 1; g_file_query_exists (top_level_dir, NULL); i++) {
    g_free (top_level_dir_basename_modified);
    g_object_unref (top_level_dir);
    if (has_only_one_file) {
      top_level_dir_basename_modified = g_strdup_printf ("%s(%d)%s",
                                                         top_level_dir_basename,
                                                         i,
                                                         pathname_extension);
    } else {
      top_level_dir_basename_modified = g_strdup_printf ("%s(%d)",
                                                         top_level_dir_basename,
                                                         i);
    }
    top_level_dir = g_file_get_child (top_level_parent_dir,
                                      top_level_dir_basename_modified);
  }

  if (!has_only_one_file)
    g_file_make_directory_with_parents (top_level_dir, NULL, &(arextract->priv->error));

  g_free (pathname_basename);
  g_free (top_level_dir_basename);
  g_free (top_level_dir_basename_modified);
  g_object_unref (top_level_parent_dir);

  if (arextract->priv->error != NULL) {
    autoar_common_g_signal_emit (in_thread, arextract,
                                 autoar_extract_signals[ERROR],
                                 0, arextract->priv->error);
    g_object_unref (top_level_dir);
    g_hash_table_unref (bad_filename);
    archive_read_free (a);
    return;
  }

  autoar_common_g_signal_emit (in_thread, arextract,
                               autoar_extract_signals[DECIDE_DEST],
                               0, top_level_dir);

  /* Step 3: Extract files
   * We have to re-open the archive to extract files */
  g_debug ("autoar_extract_run: Step 3, Extract");
  a = archive_read_new ();
  archive_read_support_filter_all (a);
  if (use_raw_format)
    archive_read_support_format_raw (a);
  else
    archive_read_support_format_all (a);
  archive_read_set_open_callback (a, libarchive_read_open_cb);
  archive_read_set_read_callback (a, libarchive_read_read_cb);
  archive_read_set_close_callback (a, libarchive_read_close_cb);
  archive_read_set_seek_callback (a, libarchive_read_seek_cb);
  archive_read_set_skip_callback (a, libarchive_read_skip_cb);
  archive_read_set_callback_data (a, arextract);
  r = archive_read_open1 (a);
  if (r != ARCHIVE_OK) {
    if (arextract->priv->error == NULL) {
      arextract->priv->error = g_error_new (autoar_extract_quark,
                                            archive_errno (a),
                                            "\'%s\': %s",
                                            arextract->priv->source,
                                            archive_error_string (a));
    }
    autoar_common_g_signal_emit (in_thread, arextract,
                                 autoar_extract_signals[ERROR],
                                 0, arextract->priv->error);
    g_object_unref (top_level_dir);
    g_hash_table_unref (bad_filename);
    archive_read_free (a);
    return;
  }
  userhash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  grouphash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  while ((r = archive_read_next_header (a, &entry)) == ARCHIVE_OK) {
    const char *pathname;
    const char *pathname_skip_prefix;
    char **pathname_chunks;

    GFile *extracted_filename;

    pathname = archive_entry_pathname (entry);
    if (GPOINTER_TO_UINT (g_hash_table_lookup (bad_filename, pathname)))
      continue;

    if (!has_only_one_file) {
      if (has_top_level_dir)
        pathname_skip_prefix = pathname + pathname_prefix_len;
      else
        pathname_skip_prefix = pathname + strspn (pathname, "./");

      for (; *pathname_skip_prefix == '/'; pathname_skip_prefix++);
      extracted_filename = g_file_get_child (top_level_dir, pathname_skip_prefix);

      /* Extracted file should not be located outside the top level directory. */
      if (!g_file_has_prefix (extracted_filename, top_level_dir)) {
        pathname_chunks = g_strsplit (pathname_skip_prefix, "/", G_MAXINT);
        for (i = 0; pathname_chunks[i] != NULL; i++) {
          if (strcmp (pathname_chunks[i], "..") == 0) {
            char *pathname_sanitized;

            *pathname_chunks[i] = '\0';
            pathname_sanitized = g_strjoinv ("/", pathname_chunks);

            g_object_unref (extracted_filename);
            extracted_filename = g_file_get_child (top_level_dir, pathname_sanitized);

            g_free (pathname_sanitized);

            if (g_file_has_prefix (extracted_filename, top_level_dir))
              break;
          }
        }
        g_strfreev (pathname_chunks);
      }
    } else {
      extracted_filename = g_object_ref (top_level_dir);
    }

    autoar_extract_do_write_entry (arextract,
                                   a,
                                   entry,
                                   extracted_filename,
                                   userhash,
                                   grouphash,
                                   in_thread,
                                   use_raw_format);

    if (arextract->priv->error != NULL) {
      autoar_common_g_signal_emit (in_thread, arextract,
                                   autoar_extract_signals[ERROR],
                                   0, arextract->priv->error);
      g_object_unref (extracted_filename);
      g_object_unref (top_level_dir);
      g_hash_table_unref (userhash);
      g_hash_table_unref (grouphash);
      g_hash_table_unref (bad_filename);
      archive_read_close (a);
      archive_read_free (a);
      return;
    }

    arextract->priv->completed_files++;
    autoar_common_g_signal_emit (in_thread,
                                 arextract,
                                 autoar_extract_signals[PROGRESS],
                                 0,
                                 ((double)(arextract->priv->completed_size)) /
                                 ((double)(arextract->priv->size)),
                                 ((double)(arextract->priv->completed_files)) /
                                 ((double)(arextract->priv->files)));
    g_object_unref (extracted_filename);
  }
  if (r != ARCHIVE_EOF) {
    if (arextract->priv->error == NULL) {
      arextract->priv->error = g_error_new (autoar_extract_quark,
                                            archive_errno (a),
                                            "\'%s\': %s",
                                            arextract->priv->source,
                                            archive_error_string (a));
    }
    autoar_common_g_signal_emit (in_thread, arextract,
                                 autoar_extract_signals[ERROR],
                                 0, arextract->priv->error);
    g_object_unref (top_level_dir);
    g_hash_table_unref (userhash);
    g_hash_table_unref (grouphash);
    g_hash_table_unref (bad_filename);
    archive_read_close (a);
    archive_read_free (a);
    return;
  }

  g_object_unref (top_level_dir);
  g_hash_table_unref (userhash);
  g_hash_table_unref (grouphash);
  g_hash_table_unref (bad_filename);
  archive_read_close (a);
  archive_read_free (a);
  if (arextract->priv->error != NULL) {
    autoar_common_g_signal_emit (in_thread, arextract,
                                 autoar_extract_signals[ERROR],
                                 0, arextract->priv->error);
    return;
  }

  /* If the extraction is completed successfully, remove the source file.
   * Errors are not fatal because we have completed our work. */
  autoar_common_g_signal_emit (in_thread, arextract,
                               autoar_extract_signals[PROGRESS],
                               0, 1.0, 1.0);
  g_debug ("autoar_extract_run: Finalize");
  if (autoar_pref_get_delete_if_succeed (arextract->priv->arpref)) {
    g_debug ("autoar_extract_run: Delete");
    source = g_file_new_for_commandline_arg (arextract->priv->source);
    g_file_delete (source, NULL, NULL);
    g_object_unref (source);
  }
  autoar_common_g_signal_emit (in_thread, arextract,
                               autoar_extract_signals[COMPLETED], 0);
}

void
autoar_extract_start (AutoarExtract *arextract)
{
  autoar_extract_run (arextract, FALSE);
}

static void
autoar_extract_start_async_thread (GTask *task,
                                   gpointer source_object,
                                   gpointer task_data,
                                   GCancellable *cancellable)
{
  AutoarExtract *arextract = source_object;
  autoar_extract_run (arextract, TRUE);
  g_task_return_pointer (task, NULL, g_free);
  g_object_unref (arextract);
}


void
autoar_extract_start_async (AutoarExtract *arextract)
{
  GTask *task;

  g_object_ref (arextract);

  task = g_task_new (arextract, NULL, NULL, NULL);
  g_task_set_task_data (task, NULL, NULL);
  g_task_run_in_thread (task, autoar_extract_start_async_thread);
}