/*
   Bacula(R) - The Network Backup Solution

   Copyright (C) 2000-2016 Kern Sibbald

   The original author of Bacula is Kern Sibbald, with contributions
   from many others, a complete list can be found in the file AUTHORS.

   You may use this file and others of this release according to the
   license defined in the LICENSE file, which includes the Affero General
   Public License, v3.0 ("AGPLv3") and some additional permissions and
   terms pursuant to its AGPLv3 Section 7.

   This notice must be preserved when any source code is
   conveyed and/or propagated.

   Bacula(R) is a registered trademark of Kern Sibbald.
 */
/**
 * Major refactoring of XATTR code written by:
 *
 *  Radosław Korzeniewski, MMXVI
 *  radoslaw@korzeniewski.net, radekk@inteos.pl
 *  Inteos Sp. z o.o. http://www.inteos.pl/
 *
 */

#include "bacula.h"
#include "filed.h"
#include "bxattr_freebsd.h"

#if defined(HAVE_FREEBSD_OS)

/* check if XATTR support is enabled */
#if defined(HAVE_XATTR)

/*
 * Define the supported XATTR streams for this OS
 */
static const int os_xattr_streams[] = {
   STREAM_XACL_FREEBSD_XATTR,
   0
};

static const int os_xattr_namespaces[] = {
   EXTATTR_NAMESPACE_USER,
   EXTATTR_NAMESPACE_SYSTEM,
   -1
};

static const char *os_xattr_acl_skiplist[] = {
   "system.posix1e.acl_access",
   "system.posix1e.acl_default",
   "system.nfs4.acl",
   NULL
};

static const char *os_xattr_skiplist[] = {
   NULL
};

/*
 * OS specific constructor
 */
BXATTR_FreeBSD::BXATTR_FreeBSD()
{
   set_xattr_streams(os_xattr_streams);
   set_xattr_skiplists(os_xattr_skiplist, os_xattr_acl_skiplist);
};

/*
 * Perform OS specific extended attribute backup
 *
 * in/out - check API at bxattr.h
 */
bRC_BXATTR BXATTR_FreeBSD::os_backup_xattr (JCR *jcr, FF_PKT *ff_pkt){

   bRC_BXATTR rc;
   POOLMEM *xlist;
   uint32_t xlen;
   char *name;
   uint32_t name_len;
   POOLMEM *value;
   uint32_t value_len;
   POOLMEM *name_gen;
   uint32_t name_gen_len;
   char * namespace_str;
   int namespace_len;
   bool skip;
   alist *xattr_list = NULL;
   int xattr_count = 0;
   uint32_t len = 0;
   BXATTR_xattr *xattr;
   int a;

   for (a = 0; os_xattr_namespaces[a] != -1; a++){ // loop through all available namespaces
      /* xlist is allocated as POOLMEM by os_get_xattr_names */
      rc = os_get_xattr_names(jcr, os_xattr_namespaces[a], &xlist, &xlen);
      switch (rc){
         case bRC_BXATTR_ok:
            /* it's ok, so go further */
            break;
         case bRC_BXATTR_skip:
         case bRC_BXATTR_cont:
            /* no xattr available, so skip rest of it */
            return bRC_BXATTR_ok;
         default:
            return rc;
      }

      /* get a string representation of the namespace */
      if (extattr_namespace_to_string(os_xattr_namespaces[a], &namespace_str) != 0){
         Mmsg2(jcr->errmsg, _("Failed to convert %d into namespace on file \"%s\"\n"), os_xattr_namespaces[a], jcr->last_fname);
         Dmsg2(100, "Failed to convert %d into namespace on file \"%s\"\n", os_xattr_namespaces[a], jcr->last_fname);
         goto bail_out;
      }
      namespace_len = strlen(namespace_str);

      /* follow the list of xattr names and get the values */
      for (name = xlist; (name - xlist) + 1 < xlen; name = strchr(name, '\0') + 1){
         name_len = strlen(name);
         name_gen = get_pool_memory(PM_FNAME);
         name_gen = check_pool_memory_size(name_gen, name_len + namespace_len + 2);
         bsnprintf(name_gen, name_len + namespace_len + 2, "%s.%s", namespace_str, name);
         name_gen_len = strlen(name_gen);

         skip =  check_xattr_skiplists(jcr, ff_pkt, name_gen);
         if (skip || name_len == 0){
            Dmsg1(100, "Skipping xattr named %s\n", name_gen);
            continue;
         }

         /* value is allocated as POOLMEM by os_get_xattr_value */
         rc = os_get_xattr_value(jcr, os_xattr_namespaces[a], name, &value, &value_len);
         switch (rc){
            case bRC_BXATTR_ok:
               /* it's ok, so go further */
               break;
            case bRC_BXATTR_skip:
               /* no xattr available, so skip rest of it */
               rc = bRC_BXATTR_ok;
               goto bail_out;
            default:
               /* error / fatal */
               goto bail_out;
         }

         /*
          * we have a name of the extended attribute in the name variable
          * and value of the extended attribute in the value variable
          * so we need to build a list
          */
         xattr = (BXATTR_xattr*)malloc(sizeof(BXATTR_xattr));
         xattr->name_len = name_gen_len;
         xattr->name = name_gen;
         xattr->value_len = value_len;
         xattr->value = value;
         /*       magic              name_len          name        value_len       value */
         len += sizeof(uint32_t) + sizeof(uint32_t) + name_gen_len + sizeof(uint32_t) + value_len;

         if (xattr_list == NULL){
            xattr_list = New(alist(10, not_owned_by_alist));
         }
         xattr_list->append(xattr);
         xattr_count++;
      }
      if (xattr_count > 0){
         /* serialize the stream */
         rc = serialize_xattr_stream(jcr, len, xattr_list);
         if (rc != bRC_BXATTR_ok){
            Mmsg(jcr->errmsg, _("Failed to serialize extended attributes on file \"%s\"\n"), jcr->last_fname);
            Dmsg1(100, "Failed to serialize extended attributes on file \"%s\"\n", jcr->last_fname);
            goto bail_out;
         } else {
            /* send data to SD */
            rc = send_xattr_stream(jcr, STREAM_XACL_FREEBSD_XATTR);
         }
      } else {
         rc = bRC_BXATTR_ok;
      }
   }
bail_out:
   /* free allocated data */
   if (xattr_list != NULL){
      foreach_alist(xattr, xattr_list){
         if (xattr == NULL){
            break;
         }
         if (xattr->name){
            free_pool_memory(name_gen);
         }
         if (xattr->value){
            free(xattr->value);
         }
         free(xattr);
      }
      delete xattr_list;
   }
   if (xlist != NULL){
      free(xlist);
   }

   return rc;
};

/*
 * Perform OS specific XATTR restore. Runtime is called only when stream is supported by OS.
 *
 * in/out - check API at bxattr.h
 */
bRC_BXATTR BXATTR_FreeBSD::os_restore_xattr (JCR *jcr, int stream, char *content, uint32_t length){
   return generic_restore_xattr(jcr, stream);
};

/*
 * Return a list of xattr names in newly allocated pool memory and a length of the allocated buffer.
 * It allocates a memory with poolmem subroutines every time a function is called, so it must be freed
 * when not needed.
 *
 * in/out - check API at bxattr.h
 *
 * As a FreeBSD uses a different attributes name schema/format then this method is a very different
 * from a standard generic method because it uses a namespace (ns) value for os dependent optimization.
 */
bRC_BXATTR BXATTR_FreeBSD::os_get_xattr_names (JCR *jcr, int ns, POOLMEM ** pxlist, uint32_t * xlen){

   int len;
   POOLMEM * list;
   int a;
   int stra;
   POOLMEM * genlist;

   /* check input data */
   if (jcr == NULL || xlen == NULL || pxlist == NULL){
      return bRC_BXATTR_inval;
   }
   /* get the length of the extended attributes */
   len = extattr_list_link(jcr->last_fname, ns, NULL, 0);
   switch (len){
      case -1: {
         berrno be;

         switch (errno){
            case ENOENT:
               /* no file available, skip it */
               return bRC_BXATTR_skip;
            case EOPNOTSUPP:
               /* no xattr supported on filesystem, clear a flag and skip it */
               clear_flag(BXATTR_FLAG_NATIVE);
               set_content(NULL);
               return bRC_BXATTR_skip;
            case EPERM:
               if (ns == EXTATTR_NAMESPACE_SYSTEM){
                  return bRC_BXATTR_cont;
               } /* else show error */
            default:
               Mmsg2(jcr->errmsg, _("extattr_list_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
               Dmsg2(100, "extattr_list_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
               return bRC_BXATTR_error;
         }
         break;
      }
      case 0:
         /* xattr available but empty, skip it */
         return bRC_BXATTR_skip;
      default:
         break;
   }

   /*
    * allocate memory for the extented attribute list
    * default size is a 4k for PM_BSOCK, which should be sufficient on almost all
    * Linux system where xattrs a limited in size to single filesystem block ~4kB
    * so we need to check required size
    */
   list = get_pool_memory(PM_BSOCK);
   list = check_pool_memory_size(list, len + 1);
   memset(list, 0, len + 1);

   /* get the list of extended attributes names for a file */
   len = extattr_list_link(jcr->last_fname, ns, list, len);
   switch (len){
   case -1: {
      berrno be;

      switch (errno){
      case ENOENT:
         /* no file available, skip it, first release allocated memory */
         free_pool_memory(list);
         return bRC_BXATTR_skip;
         case EPERM:
            if (ns == EXTATTR_NAMESPACE_SYSTEM){
               return bRC_BXATTR_cont;
            } /* else show error */
      default:
         Mmsg2(jcr->errmsg, _("extattr_list_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
         Dmsg2(100, "extattr_list_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
         free_pool_memory(list);
         return bRC_BXATTR_error;
      }
      break;
   }
   default:
      break;
   }
   /* convert FreeBSD list type to the generic one */
   genlist = get_pool_memory(PM_BSOCK);
   genlist = check_pool_memory_size(genlist, len + 1);
   memset(genlist, 0, len + 1);
   for (a = 0; a < len; a += list[a] + 1){
      stra = list[a];
      memcpy(genlist + a, list + a + 1, stra);
      genlist[a + stra] = '\0';
   }
   free_pool_memory(list);
   /* setup return data */
   *pxlist = genlist;
   *xlen = len;
   return bRC_BXATTR_ok;
};

/*
 * Return a value of the requested attribute name and a length of the allocated buffer.
 * It allocates a memory with poolmem subroutines every time a function is called, so it must be freed
 * when not needed.
 *
 * in/out - check API at bxattr.h
 *
 * As a FreeBSD uses a different attributes name schema/format then this method is a very different
 * from a standard generic method because it uses a namespace (ns) value for os dependent optimization.
 */
bRC_BXATTR BXATTR_FreeBSD::os_get_xattr_value (JCR *jcr, int ns, char * name, char ** pvalue, uint32_t * plen){

   int len;
   POOLMEM * value;

   /* check input data */
   if (jcr == NULL || name == NULL || plen == NULL || pvalue == NULL){
      return bRC_BXATTR_inval;
   }
   /* get the length of the value for extended attribute */
   len = extattr_get_link(jcr->last_fname, ns, name, NULL, 0);
   switch (len){
      case -1: {
         berrno be;

         switch (errno){
            case ENOENT:
               /* no file available, skip it */
               return bRC_BXATTR_skip;
            default:
               /* XXX: what about ENOATTR error value? */
               Mmsg2(jcr->errmsg, _("extattr_get_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
               Dmsg2(100, "extattr_get_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
               return bRC_BXATTR_error;
         }
         break;
      }
      default:
         break;
   }

   if (len > 0){
      /*
       * allocate memory for the extented attribute value
       * default size is a 256B for PM_MESSAGE, so we need to check required size
       */
      value = get_pool_memory(PM_MESSAGE);
      value = check_pool_memory_size(value, len + 1);
      memset(value, 0, len + 1);
      /* value is not empty, get a data */
      len = extattr_get_link(jcr->last_fname, ns, name, value, len);
      switch (len){
      case -1: {
         berrno be;

         switch (errno){
         case ENOENT:
            /* no file available, skip it, first release allocated memory */
            free_pool_memory(value);
            return bRC_BXATTR_skip;
         default:
            Mmsg2(jcr->errmsg, _("extattr_get_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
            Dmsg2(100, "extattr_get_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
            free_pool_memory(value);
            return bRC_BXATTR_error;
         }
         break;
      }
      default:
         break;
      }
      /* ensure a value is nul terminated */
      value[len] = '\0';
   } else {
      /* empty value */
      value = NULL;
      len = 0;
   }
   /* setup return data */
   *pvalue = value;
   *plen = len;
   return bRC_BXATTR_ok;
};

/*
 * Low level OS specific runtime to set extended attribute on file
 *
 * in/out - check API at bxattr.h
 *
 * xattr->name should be in '<namespace>.<name>' format which
 * function handle without problem, otherwise it returns an error
 * TODO: it is possible to handle a different attributes name format
 * for OS portability where default namespace 'user' can be used
 */
bRC_BXATTR BXATTR_FreeBSD::os_set_xattr (JCR *jcr, BXATTR_xattr *xattr){

   char * name;
   char * nspace;
   int ns;
   int rc;

   /* check input data */
   if (jcr == NULL || xattr == NULL){
      return bRC_BXATTR_inval;
   }

   /* search for attribute namespace which is distinguished from attribute name by a dot '.' character */
   if ((name = strchr(xattr->name, '.')) == (char *)NULL){
      Mmsg2(jcr->errmsg, _("Failed to split %s into namespace and name part on file \"%s\"\n"), xattr->name, jcr->last_fname);
      Dmsg2(100, "Failed to split %s into namespace and name part on file \"%s\"\n", xattr->name, jcr->last_fname);
      return bRC_BXATTR_error;
   }

   /* split namespace and name of the attribute */
   nspace = xattr->name;
   *name++ = '\0';

   /* check if namespace is valid on this system */
   if (extattr_string_to_namespace(nspace, &ns) != 0){
      Mmsg2(jcr->errmsg, _("Failed to convert %s into namespace on file \"%s\"\n"), nspace, jcr->last_fname);
      Dmsg2(100, "Failed to convert %s into namespace on file \"%s\"\n", nspace, jcr->last_fname);
      return bRC_BXATTR_error;
   }

   /* set extattr on file */
   rc = extattr_set_link(jcr->last_fname, ns, name, xattr->value, xattr->value_len);
   if (rc < 0 || rc != (int)xattr->value_len){
      berrno be;

      switch (errno){
      case ENOENT:
         break;
      default:
         Mmsg2(jcr->errmsg, _("extattr_set_link error on file \"%s\": ERR=%s\n"), jcr->last_fname, be.bstrerror());
         Dmsg2(100, "extattr_set_link error file=%s ERR=%s\n", jcr->last_fname, be.bstrerror());
         return bRC_BXATTR_error;
      }
   }
   return bRC_BXATTR_ok;
};

#endif /* HAVE_XATTR */

#endif /* HAVE_FREEBSD_OS */
