/*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
 
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
 const bool have_lzo = false;
 #endif
 
-
 static void deallocate_cipher(r_ctx &rctx);
 static void deallocate_fork_cipher(r_ctx &rctx);
 static void free_signature(r_ctx &rctx);
 static void free_session(r_ctx &rctx);
-static void close_previous_stream(r_ctx &rctx);
-
+static bool close_previous_stream(JCR *jcr, r_ctx &rctx);
 static bool verify_signature(JCR *jcr, r_ctx &rctx);
 int32_t extract_data(JCR *jcr, BFILE *bfd, POOLMEM *buf, int32_t buflen,
                      uint64_t *addr, int flags, int32_t stream, RESTORE_CIPHER_CTX *cipher_ctx);
 }
 
 #ifdef HAVE_DARWIN_OS
-bool restore_finderinfo(JCR *jcr, POOLMEM *buf, int32_t buflen)
+static bool restore_finderinfo(JCR *jcr, POOLMEM *buf, int32_t buflen)
 {
    struct attrlist attrList;
 
    return true;
 }
 #else
-bool restore_finderinfo(JCR *jcr, POOLMEM *buf, int32_t buflen)
+static bool restore_finderinfo(JCR *jcr, POOLMEM *buf, int32_t buflen)
 {
    return true;
 }
 #endif
 
+/*
+ * Cleanup of delayed restore stack with streams for later
+ * processing.
+ */
+static inline void drop_delayed_restore_streams(r_ctx &rctx, bool reuse)
+{
+   RESTORE_DATA_STREAM *rds;
+
+   if (!rctx.delayed_streams ||
+       rctx.delayed_streams->empty()) {
+      return;
+   }
+
+   foreach_alist(rds, rctx.delayed_streams) {
+      free(rds->content);
+   }
+
+   rctx.delayed_streams->destroy();
+   if (reuse) {
+      rctx.delayed_streams->init(10, owned_by_alist);
+   }
+}
+
+/*
+ * Push a data stream onto the delayed restore stack for
+ * later processing.
+ */
+static inline void push_delayed_restore_stream(r_ctx &rctx, BSOCK *sd)
+{
+   RESTORE_DATA_STREAM *rds;
+
+   if (!rctx.delayed_streams) {
+      rctx.delayed_streams = New(alist(10, owned_by_alist));
+   }
+
+   rds = (RESTORE_DATA_STREAM *)malloc(sizeof(RESTORE_DATA_STREAM));
+   rds->stream = rctx.stream;
+   rds->content = (char *)malloc(sd->msglen);
+   memcpy(rds->content, sd->msg, sd->msglen);
+   rds->content_length = sd->msglen;
+
+   rctx.delayed_streams->append(rds);
+}
+
+/*
+ * Perform a restore of an ACL using the stream received.
+ * This can either be a delayed restore or direct restore.
+ */
+static inline bool do_restore_acl(JCR *jcr,
+                                  int stream)
+{
+   switch (parse_acl_streams(jcr, stream)) {
+   case bacl_exit_fatal:
+      return false;
+   case bacl_exit_error:
+      /*
+       * Non-fatal errors, count them and when the number is under ACL_REPORT_ERR_MAX_PER_JOB
+       * print the error message set by the lower level routine in jcr->errmsg.
+       */
+      if (jcr->acl_data->nr_errors < ACL_REPORT_ERR_MAX_PER_JOB) {
+         Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+      }
+      jcr->acl_data->nr_errors++;
+      break;
+   case bacl_exit_ok:
+      break;
+   }
+   return true;
+}
+
+/*
+ * Perform a restore of an XATTR using the stream received.
+ * This can either be a delayed restore or direct restore.
+ */
+static inline bool do_restore_xattr(JCR *jcr,
+                                    int stream)
+{
+   switch (parse_xattr_streams(jcr, stream)) {
+   case bxattr_exit_fatal:
+      return false;
+   case bxattr_exit_error:
+      /*
+       * Non-fatal errors, count them and when the number is under XATTR_REPORT_ERR_MAX_PER_JOB
+       * print the error message set by the lower level routine in jcr->errmsg.
+       */
+      if (jcr->xattr_data->nr_errors < XATTR_REPORT_ERR_MAX_PER_JOB) {
+         Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+      }
+      jcr->xattr_data->nr_errors++;
+      break;
+   case bxattr_exit_ok:
+      break;
+   }
+   return true;
+}
+
+/*
+ * Restore any data streams that are restored after the file
+ * is fully restored and has its attributes restored. Things
+ * like acls and xattr are restored after we set the file
+ * attributes otherwise we might clear some security flags
+ * by setting the attributes.
+ */
+static inline bool pop_delayed_data_streams(JCR *jcr, r_ctx &rctx)
+{
+   RESTORE_DATA_STREAM *rds;
+
+   /*
+    * See if there is anything todo.
+    */
+   if (!rctx.delayed_streams ||
+        rctx.delayed_streams->empty()) {
+      return true;
+   }
+
+   /*
+    * Only process known delayed data streams here.
+    * If you start using more delayed data streams
+    * be sure to add them in this loop and add the
+    * proper calls here.
+    *
+    * Currently we support delayed data stream
+    * processing for the following type of streams:
+    * - *_ACL_*
+    * - *_XATTR_*
+    */
+   foreach_alist(rds, rctx.delayed_streams) {
+      switch (rds->stream) {
+      case STREAM_UNIX_ACCESS_ACL:
+      case STREAM_UNIX_DEFAULT_ACL:
+      case STREAM_ACL_AIX_TEXT:
+      case STREAM_ACL_DARWIN_ACCESS_ACL:
+      case STREAM_ACL_FREEBSD_DEFAULT_ACL:
+      case STREAM_ACL_FREEBSD_ACCESS_ACL:
+      case STREAM_ACL_HPUX_ACL_ENTRY:
+      case STREAM_ACL_IRIX_DEFAULT_ACL:
+      case STREAM_ACL_IRIX_ACCESS_ACL:
+      case STREAM_ACL_LINUX_DEFAULT_ACL:
+      case STREAM_ACL_LINUX_ACCESS_ACL:
+      case STREAM_ACL_TRU64_DEFAULT_ACL:
+      case STREAM_ACL_TRU64_DEFAULT_DIR_ACL:
+      case STREAM_ACL_TRU64_ACCESS_ACL:
+      case STREAM_ACL_SOLARIS_ACLENT:
+      case STREAM_ACL_SOLARIS_ACE:
+      case STREAM_ACL_AFS_TEXT:
+      case STREAM_ACL_AIX_AIXC:
+      case STREAM_ACL_AIX_NFS4:
+      case STREAM_ACL_FREEBSD_NFS4_ACL:
+         pm_memcpy(jcr->acl_data->content, rds->content, rds->content_length);
+         jcr->acl_data->content_length = rds->content_length;
+         if (!do_restore_acl(jcr, rds->stream)) {
+            goto bail_out;
+         }
+         free(rds->content);
+         break;
+      case STREAM_XATTR_IRIX:
+      case STREAM_XATTR_TRU64:
+      case STREAM_XATTR_AIX:
+      case STREAM_XATTR_OPENBSD:
+      case STREAM_XATTR_SOLARIS_SYS:
+      case STREAM_XATTR_DARWIN:
+      case STREAM_XATTR_FREEBSD:
+      case STREAM_XATTR_LINUX:
+      case STREAM_XATTR_NETBSD:
+         pm_memcpy(jcr->xattr_data->content, rds->content, rds->content_length);
+         jcr->xattr_data->content_length = rds->content_length;
+         if (!do_restore_xattr(jcr, rds->stream)) {
+            goto bail_out;
+         }
+         free(rds->content);
+         break;
+      default:
+         Jmsg(jcr, M_WARNING, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
+              rds->stream);
+         break;
+      }
+   }
+
+   /*
+    * We processed the stack so we can destroy it.
+    */
+   rctx.delayed_streams->destroy();
+
+   /*
+    * (Re)Initialize the stack for a new use.
+    */
+   rctx.delayed_streams->init(10, owned_by_alist);
+
+   return true;
+
+bail_out:
+
+   /*
+    * Destroy the content of the stack and (re)initialize it for a new use.
+    */
+   drop_delayed_restore_streams(rctx, true);
+
+   return false;
+}
+
 /*
  * Restore the requested files.
  */
          /*
           * if any previous stream open, close it
           */
-         close_previous_stream(rctx);
+         if (!close_previous_stream(jcr, rctx)) {
+            goto bail_out;
+         }
 
          /*
           * TODO: manage deleted files
           * b)     and it is not a directory (they are never "extracted")
           * c) or the file name is empty
           */
-         if ((!rctx.extract && jcr->last_type != FT_DIREND) || (*jcr->last_fname == 0)) {
+         if ((!rctx.extract &&
+               jcr->last_type != FT_DIREND) ||
+             (*jcr->last_fname == 0)) {
             break;
          }
          if (have_acl) {
-            pm_memcpy(jcr->acl_data->content, sd->msg, sd->msglen);
-            jcr->acl_data->content_length = sd->msglen;
-            switch (parse_acl_streams(jcr, rctx.stream)) {
-            case bacl_exit_fatal:
-               goto bail_out;
-            case bacl_exit_error:
-               /*
-                * Non-fatal errors, count them and when the number is under ACL_REPORT_ERR_MAX_PER_JOB
-                * print the error message set by the lower level routine in jcr->errmsg.
-                */
-               if (jcr->acl_data->nr_errors < ACL_REPORT_ERR_MAX_PER_JOB) {
-                  Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
+            /*
+             * For anything that is not a directory we delay
+             * the restore of acls till a later stage.
+             */
+            if (jcr->last_type != FT_DIREND) {
+               push_delayed_restore_stream(rctx, sd);
+            } else {
+               pm_memcpy(jcr->acl_data->content, sd->msg, sd->msglen);
+               jcr->acl_data->content_length = sd->msglen;
+               if (!do_restore_acl(jcr, rctx.stream)) {
+                  goto bail_out;
                }
-               jcr->acl_data->nr_errors++;
-               break;
-            case bacl_exit_ok:
-               break;
             }
          } else {
             non_support_acl++;
       case STREAM_XATTR_AIX:
       case STREAM_XATTR_OPENBSD:
       case STREAM_XATTR_SOLARIS_SYS:
-      case STREAM_XATTR_SOLARIS:
       case STREAM_XATTR_DARWIN:
       case STREAM_XATTR_FREEBSD:
       case STREAM_XATTR_LINUX:
           * b)     and it is not a directory (they are never "extracted")
           * c) or the file name is empty
           */
-         if ((!rctx.extract && jcr->last_type != FT_DIREND) || (*jcr->last_fname == 0)) {
+         if ((!rctx.extract &&
+               jcr->last_type != FT_DIREND) ||
+             (*jcr->last_fname == 0)) {
+            break;
+         }
+         if (have_xattr) {
+            /*
+             * For anything that is not a directory we delay
+             * the restore of xattr till a later stage.
+             */
+            if (jcr->last_type != FT_DIREND) {
+               push_delayed_restore_stream(rctx, sd);
+            } else {
+               pm_memcpy(jcr->xattr_data->content, sd->msg, sd->msglen);
+               jcr->xattr_data->content_length = sd->msglen;
+               if (!do_restore_xattr(jcr, rctx.stream)) {
+                  goto bail_out;
+               }
+            }
+         } else {
+            non_support_xattr++;
+         }
+         break;
+
+      case STREAM_XATTR_SOLARIS:
+         /*
+          * Do not restore Extended Attributes when
+          * a) The current file is not extracted
+          * b)     and it is not a directory (they are never "extracted")
+          * c) or the file name is empty
+          */
+         if ((!rctx.extract &&
+               jcr->last_type != FT_DIREND) ||
+             (*jcr->last_fname == 0)) {
             break;
          }
          if (have_xattr) {
             pm_memcpy(jcr->xattr_data->content, sd->msg, sd->msglen);
             jcr->xattr_data->content_length = sd->msglen;
-            switch (parse_xattr_streams(jcr, rctx.stream)) {
-            case bxattr_exit_fatal:
+            if (!do_restore_xattr(jcr, rctx.stream)) {
                goto bail_out;
-            case bxattr_exit_error:
-               /*
-                * Non-fatal errors, count them and when the number is under XATTR_REPORT_ERR_MAX_PER_JOB
-                * print the error message set by the lower level routine in jcr->errmsg.
-                */
-               if (jcr->xattr_data->nr_errors < XATTR_REPORT_ERR_MAX_PER_JOB) {
-                  Jmsg(jcr, M_WARNING, 0, "%s", jcr->errmsg);
-               }
-               jcr->xattr_data->nr_errors++;
-               break;
-            case bxattr_exit_ok:
-               break;
             }
          } else {
             non_support_xattr++;
          break;
 
       case STREAM_PLUGIN_NAME:
-         close_previous_stream(rctx);
+         if (!close_previous_stream(jcr, rctx)) {
+            goto bail_out;
+         }
          Dmsg1(50, "restore stream_plugin_name=%s\n", sd->msg);
          plugin_name_stream(jcr, sd->msg);
          break;
          break;                    /* these are sent by Director */
 
       default:
-         close_previous_stream(rctx);
+         if (!close_previous_stream(jcr, rctx)) {
+            goto bail_out;
+         }
          Jmsg(jcr, M_WARNING, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
               rctx.stream);
          Dmsg2(0, "Unknown stream=%d data=%s\n", rctx.stream, sd->msg);
       bclose_chksize(jcr, &rctx.forkbfd, rctx.fork_size);
    }
 
-   close_previous_stream(rctx);
+   if (!close_previous_stream(jcr, rctx)) {
+      goto bail_out;
+   }
    jcr->setJobStatus(JS_Terminated);
    goto ok_out;
 
       jcr->xattr_data = NULL;
    }
 
+   /*
+    * Free the delayed stream stack list.
+    */
+   if (rctx.delayed_streams) {
+      drop_delayed_restore_streams(rctx, false);
+      delete rctx.delayed_streams;
+   }
+
    bclose(&rctx.forkbfd);
    bclose(&rctx.bfd);
    free_attr(rctx.attr);
 
 bool decompress_data(JCR *jcr, int32_t stream, char **data, uint32_t *length)
 {
+#if defined(HAVE_LZO) || defined(HAVE_LIBZ)
    char ec1[50]; /* Buffer printing huge values */
+#endif
 
    Dmsg1(200, "Stream found in decompress_data(): %d\n", stream);
    if(stream == STREAM_COMPRESSED_DATA || stream == STREAM_SPARSE_COMPRESSED_DATA || stream == STREAM_WIN32_COMPRESSED_DATA
    return -1;
 }
 
-
 /*
  * If extracting, close any previous stream
  */
-static void close_previous_stream(r_ctx &rctx)
+static bool close_previous_stream(JCR *jcr, r_ctx &rctx)
 {
    /*
     * If extracting, it was from previous stream, so
       }
       rctx.extract = false;
 
+      /*
+       * Now perform the delayed restore of some specific data streams.
+       */
+      if (!pop_delayed_data_streams(jcr, rctx)) {
+         return false;
+      }
+
       /*
        * Verify the cryptographic signature, if any
        */
       Dmsg0(000, "=== logic error !open\n");
       bclose(&rctx.bfd);
    }
-}
 
+   return true;
+}
 
 /*
  * In the context of jcr, flush any remaining data from the cipher context,