//----------------------------------------------------------------------------
// Copyright 2023-2024 Joe Lowe
//
// Permission is granted to any person obtaining a copy of this Software,
// to deal in the Software without restriction, including the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and sell copies of
// the Software.
//
// The above copyright and permission notice must be left intact in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS WITHOUT WARRANTY.
//----------------------------------------------------------------------------
// file name:  tempfs.c
// created:    2023.08.18
//    Temporary file system application sample. Demonstrates
//    implementing a full read+write file system in a standalone
//    application. Includes support for symlinks, hardlinks,
//    macos attributes and resource fork.
//
//    This sample should compile with MSVC (DevStudio) on Windows,
//    and GCC or LLVM/Clang on Windows, Mac, and Linux.
//----------------------------------------------------------------------------

#include "../common/portability.c"

#define FSRAPI_RUNTIME_LINK fsrapi
#include "fsrformatter.h"

enum { file_data_block_size = 0x4000 };
#define file_max_file_size (scast( uint64_t, file_data_block_size) * (0u + INT_MAX))
enum { file_max_resource_size = 0x2000000 };
#if INTPTR_MAX > 0xFFFFFFFF
#define volume_max_capacity scast( uint64_t, 0x100000000000) /*16TiB*/
#else
#define volume_max_capacity scast( uint64_t, 0xB0000000) /*2.75Gib*/
#endif

typedef struct file_t file_t;
typedef struct name_t name_t;

typedef struct volume_t volume_t;
struct volume_t
{
   FsrFormatter vtbl;
   int ref_count;
   size_t lister_extension_size;
   size_t file_extension_size;
   file_t* root;
   uint64_t capacity;
};

volume_t* volume_vcast( FsrFormatter* volume_)
{
   return containerof( volume_t, vtbl, volume_);
}

struct file_t
{
   FsrFile vtbl;
   volume_t* volume;
   int ref_count;
   int open_count;
   name_t* first_name;
   name_t* first_child;
   size_t data_block_count;
   uint8_t** data_block_list;
   size_t resource_size;
   uint8_t* resource_data;
   int64_t file_id;
   int64_t write_time;
   int64_t create_time;
   uint64_t file_size;
   uint8_t file_type;
   uint8_t file_flags;
   uint8_t extra_flags;
   uint8_t color;
};

file_t* file_vcast( FsrFile* file_)
{
   return containerof( file_t, vtbl, file_);
}

char file_self_type_id[] = "";

file_t* file_dcast( FsrFile* file_)
{
   file_t* file;
   (*file_)->DynaCast( file_, &file, file_self_type_id);
   return file;
}

typedef struct lister_t lister_t;
struct lister_t
{
   FsrList vtbl;
   file_t* file;
   int ref_count;
   size_t pos;
};

lister_t* lister_vcast( FsrList* lister_)
{
   return containerof( lister_t, vtbl, lister_);
}

struct name_t
{
   file_t* file;
   name_t* file_next;
   file_t* parent;
   name_t* parent_next;
   char end_name[];
};

void volume_retain( volume_t* volume)
{
   ++ volume->ref_count;
}

void volume_release( volume_t* volume)
{
   if (-- volume->ref_count == 0)
   {
      free( volume);
   }
}

void file_retain( file_t* file)
{
   ++ file->ref_count;
}

void file_release( file_t* file)
{
   volume_t* volume = file->volume;
   size_t i;
   if (-- file->ref_count == 0)
   {
      ASSERT( !file->first_name && !file->first_child);
      for (i = 0; i < file->data_block_count; i ++)
      {
         if (!file->data_block_list[i]) continue;
         volume->capacity -= file_data_block_size;
         free( file->data_block_list[i]);
         file->data_block_list[i] = nullptr;
      }
      free( file->data_block_list);
      free( file->resource_data);
      volume_release( file->volume);
      free( file);
   }
}

file_t* file_dyna_retain( FsrFile* file_)
{
   file_t* file = file_dcast( file_);
   file_retain( file);
   return file;
}


void lister_retain( lister_t* lister)
{
   ++ lister->ref_count;
}

void lister_release( lister_t* lister)
{
   if (-- lister->ref_count == 0)
   {
      file_release( lister->file);
      free( lister);
   }
}

name_t* name_factory( file_t* file, file_t* parent, const char* end_name, int64_t write_time)
{
   size_t end_name_len = strlen( end_name);
   name_t* name = calloc( 1, sizeof( name_t) + end_name_len + 1);
   name->file = file;
   name->parent = parent;
   strcpy( name->end_name, end_name);
   name_t** prev = &file->first_name;
   while (*prev) prev = &(*prev)->file_next;
   *prev = name;
   if (!name->file_next) file_retain( file);
   prev = &parent->first_child;
   while (*prev) prev = &(*prev)->parent_next;
   *prev = name;
   parent->write_time = write_time;
   return name;
}

void name_delete( name_t* name, int64_t write_time)
{
   file_t* file = name->file;
   file_t* parent = name->parent;
   name_t** prev;
   prev = &file->first_name;
   while (*prev != name) prev = &(*prev)->file_next;
   *prev = name->file_next;
   if (!*prev) file_release( file);
   prev = &parent->first_child;
   while (*prev != name) prev = &(*prev)->parent_next;
   *prev = name->parent_next;
   parent->write_time = write_time;
}

void file_get_attribs( file_t* file, FsrAttribs* attribs)
{
   attribs->fileType = file->file_type;
   attribs->fileFlags = file->file_flags;
   attribs->extraFlags = file->extra_flags;
   attribs->color = file->color;
   attribs->resourceSize = scast( unsigned, file->resource_size);
   attribs->fileId = file->file_id;
   attribs->fileSize = file->file_size;
   attribs->createTime = file->create_time;
   attribs->writeTime = file->write_time;
   if (file->file_type == fsrFileTypeSymlink)
   {
      attribs->fileSize = 0;
   }
}

void file_get_open_attribs( file_t* file, FsrOpenAttribs* open_attribs)
{
   open_attribs->accessLevel = fsrAccessLevelWriteData;
   file_get_attribs( file, &open_attribs->attribs);
}

FsrFile* file_open_retain( file_t* file)
{
   if (++ file->open_count == 1) file_retain( file);
   return &file->vtbl;
}

int/*perr*/ name_check_folder_delete( name_t* name)
{
   int perr = 0;
   file_t* file = name->file;
      // Do not allow non empty folders to be deleted.
      //
      // Gray area, is it correct to allow deleting a name of a non
      // empty folder if the folder has other names (hard links)? NTFS
      // probably fails this but I can not say I have actually tested
      // it. Nix systems generally disallow hard links for folders so
      // not an issue. Going to allow the delete here.
   if (file->first_name == name && !name->file_next && file->first_child)
   {
      perr = fsrErrorNotEmpty;
   }
   return perr;
}

int/*perr*/ file_check_move_loop( file_t* file, file_t* target_parent)
{
      // Recursively scan all parent files of target. Fail
      // if target is below source in name space. This is
      // used in move logic to prevent loosing files when
      // they are moved into a child, and to prevent
      // creation of links that create loops in the name
      // space.
   int perr = 0;
   name_t* name;
   size_t sp;
   name_t* stack[300];
   if (target_parent == file)
   {
      perr = fsrErrorInvalid;
      goto done;
   }
   if (!file->first_child) goto done;
   name = target_parent->first_name;
   if (!name) goto done;
   stack[0] = name;
   sp = 1;
   while (sp)
   {
      name = stack[ -- sp];
      if ((stack[ sp] = name->file_next) != nullptr) sp ++;
      if (name->parent == file)
      {
         perr = fsrErrorInvalid;
         goto done;
      }
      name = name->parent->first_name;
      if (!name) continue;
      if (sp >= countof( stack))
      {
            // Too many names/depth.
         perr = fsrErrorInvalid;
         goto done;
      }
      stack[ sp] = name;
      sp ++;
   }
done:
   return perr;
}

int/*perr*/ file_find_name( file_t* file, name_t** out_name, int64_t parent_file_id, const char* end_name)
{
   int perr = 0;
   name_t* name = file->first_name;
   while (name && (name->parent->file_id != parent_file_id || strcmpf( name->end_name, end_name) != 0)) name = name->file_next;
   if (!name) perr = fsrErrorDeleted;
   *out_name = name;
   return perr;
}

void file_delete_children( file_t* file)
{
   name_t* name;
   file_t* child;
   while ((name = file->first_child) != nullptr)
   {
      child = name->file;
      file_delete_children( child);
      name_delete( name, 0);
   }
}

int/*perr*/ volume_find_name( volume_t* volume, name_t** out_name, file_t** out_parent, size_t* out_link_name_part_count, const char** name_parts, size_t name_part_count)
{
   int perr = 0;
   name_t* name = nullptr;
   file_t* parent = volume->root;
   size_t name_part_index = 0;
   if (!name_part_count)
   {
         // Root has no name.
      perr = fsrErrorInvalid;
      goto done;
   }
   for (;;)
   {
      name = parent->first_child;
      while (name && strcmpf( name_parts[ name_part_index], name->end_name) != 0) name = name->parent_next;
      if (name)
      {
         if (++ name_part_index < name_part_count && name->file->file_type != fsrFileTypeSymlink)
         {
            parent = name->file;
            continue;
         }
         break;
      }
      if (parent->file_type != fsrFileTypeFolder)
      {
         perr = fsrErrorNotAFolder;
      }
      else if (name_part_index + 1 < name_part_count)
      {
         perr = fsrErrorParentNotFound;
      }
      break;
   }
done:
   *out_name = name;
   *out_parent = parent;
   *out_link_name_part_count = name ? (name_part_count - name_part_index) : 0;
   return perr;
}

int/*perr*/ file_prep_set_data_size( file_t* file, uint64_t new_file_size)
{
   int perr = 0;
   if (new_file_size > file_max_file_size)
   {
      perr = fsrErrorNoSpace;
      goto done;
   }
   size_t new_block_count = scast( size_t, (new_file_size + file_data_block_size - 1) / file_data_block_size);
   if (new_block_count <= file->data_block_count) goto done;
   new_block_count += new_block_count / 4 + 4;
   uint8_t** new_block_list = calloc( sizeof( new_block_list[0]), new_block_count);
   if (!new_block_list)
   {
      perr = fsrErrorNoSpace;
      goto done;
   }
   memcpy( new_block_list, file->data_block_list, sizeof( new_block_list[0]) * file->data_block_count);
   free( file->data_block_list);
   file->data_block_list = new_block_list;
   file->data_block_count = new_block_count;
done:
   return perr;
}

int/*perr*/ file_set_data_size( file_t* file, uint64_t new_file_size)
{
   int perr;
   uint8_t* block;
   size_t block_index;
   uint64_t zero_off;
   size_t block_count;
   perr = file_prep_set_data_size( file, new_file_size);
   if (perr) goto done;
   if (new_file_size == file->file_size) goto done;
   if (new_file_size < file->file_size)
   {
         // Zero from new file size to lesser of old file size or the
         // end of the new end block.
      block_index = scast( size_t, (new_file_size + file_data_block_size - 1) / file_data_block_size);
      zero_off = scast( uint64_t, block_index) * file_data_block_size;
      ASSERT( zero_off - new_file_size < file_data_block_size);
      if (zero_off > new_file_size)
      {
         block = file->data_block_list[ block_index - 1];
         if (block) memset( block + scast( size_t, new_file_size % file_data_block_size), 0, scast( size_t, zero_off - new_file_size));
      }
         // Free any blocks past the new end block.
      block_count = scast( size_t, (file->file_size + file_data_block_size - 1) / file_data_block_size);
      while (block_index < block_count)
      {
         block = file->data_block_list[ block_index];
         if (block)
         {
            file->volume->capacity -= file_data_block_size;
            free( block);
            file->data_block_list[ block_index] = nullptr;
         }
         block_index ++;
      }
   }
   file->file_size = new_file_size;
done:
   return perr;
}

int/*perr*/ file_write_data( file_t* file, uint64_t in_off, const pt_iobufv_t* buf, size_t buf_off, size_t requested_size, size_t* out_actual_size)
{
   int perr = 0;
   volume_t* volume = file->volume;
   uint64_t start_off = in_off;
   uint64_t end_off = in_off;
   uint64_t off = start_off;
   size_t block_index;
   size_t block_off;
   size_t part;
   uint8_t* block;
   if (start_off < file_max_file_size)
   {
      end_off = off + requested_size;
      if (end_off > file_max_file_size) end_off = file_max_file_size;
      perr = file_prep_set_data_size( file, end_off);
      if (perr)
      {
         end_off = start_off;
         goto done;
      }
   }
   while (off < end_off)
   {
      block_index = scast( size_t, off / file_data_block_size);
      block_off = scast( size_t, off % file_data_block_size);
      part = file_data_block_size - block_off;
      if (off + part > end_off) part = scast( size_t, end_off - off);
      block = file->data_block_list[ block_index];
      if (!block && volume->capacity + file_data_block_size <= volume_max_capacity)
      {
         block = file->data_block_list[ block_index] = malloc( file_data_block_size);
         if (block)
         {
            volume->capacity += file_data_block_size;
            if (part != file_data_block_size) memset( block, 0, file_data_block_size);
         }
      }
      if (!block)
      {
         end_off = off;
      }
      else
      {
         pt_iobufv_src_copy( buf, buf_off + scast( size_t, off - start_off), block + block_off, part);
         off += part;
         if (off > file->file_size)
         {
            file->file_size = off;
         }
      }
   }
done:
   if (out_actual_size) *out_actual_size = scast( size_t, end_off - start_off);
   return perr;
}

void file_read_data( file_t* file, uint64_t in_off, pt_iobufv_t* buf, size_t buf_off, size_t requested_size, size_t* out_actual_size)
{
   uint64_t off = in_off;
   uint64_t start_off = in_off;
   uint64_t end_off = in_off;
   size_t block_index;
   size_t block_off;
   size_t part;
   uint8_t* block;
   if (off < file->file_size)
   {
      end_off = off + requested_size;
      if (end_off < start_off || end_off > file->file_size)
      {
         end_off = file->file_size;
      }
   }
   off = start_off;
   while (off < end_off)
   {
      block_index = scast( size_t, off / file_data_block_size);
      block_off = scast( size_t, off % file_data_block_size);
      part = file_data_block_size - block_off;
      if (off + part > end_off) part = scast( size_t, end_off - off);
      block = file->data_block_list[ block_index];
      if (block)
      {
         pt_iobufv_dest_copy( buf, buf_off + scast( size_t, off - start_off), block + block_off, part);
      }
      else
      {
         pt_iobufv_zero( buf, buf_off + scast( size_t, off - start_off), part);
      }
      off += part;
   }
   if (out_actual_size) *out_actual_size = scast( size_t, end_off - start_off);
}

void lister_v_DynaCast( FsrList* lister_, void* v, const char* type_id)
{
   lister_t* lister = lister_vcast( lister_);
   if (!type_id) *(void**)v = lister + 1;
   else          *(void**)v = nullptr;
}

void lister_v_Release( FsrList* lister_)
{
   lister_t* lister = lister_vcast( lister_);
   lister_release( lister);
}

void lister_v_List( FsrList* lister_, FsrListOp* op, void* unused)
{
   lister_t* lister = lister_vcast( lister_);
   if (lister->pos == SIZE_MAX) goto done;
   FsrAttribs attribs; memset( &attribs, 0, sizeof( attribs));
   size_t pos = 0;
   file_t* child;
   name_t* name = lister->file->first_child;
   for (;;)
   {
      if (!name)
      {
         lister->pos = SIZE_MAX;
         goto done;
      }
      if (pos >= lister->pos)
      {
         child = name->file;
         file_get_attribs( child, &attribs);
         if (!op->add( op, &attribs, name->end_name))
         {
            lister->pos = pos;
            goto done;
         }
      }
      name = name->parent_next;
      pos ++;
   }
done:
   op->result.noMore = (lister->pos == SIZE_MAX);
   op->complete( op, 0);

}

FsrList_vtbl_t lister_vtbl = {
   .DynaCast = lister_v_DynaCast,
   .Release  = lister_v_Release ,
   .List     = lister_v_List    ,
   };

lister_t* lister_factory( file_t* file)
{
   lister_t* lister = calloc( 1, sizeof( lister_t) + file->volume->lister_extension_size);
   lister->vtbl = &lister_vtbl;
   file_retain( file);
   lister->file = file;
   lister->ref_count = 1;
   return lister;
}

void file_init( file_t* file, uint8_t file_type, uint8_t file_flags, int64_t write_time)
{
   file->file_type = file_type;
   file->file_flags = file_flags;
   file->write_time = write_time;
   file->create_time = write_time;
}

void file_v_DynaCast( FsrFile* file_, void* v, const char* type_id)
{
   file_t* file = file_vcast( file_);
   if (!type_id) *(void**)v = file + 1;
   else if (type_id == file_self_type_id) *(file_t**)v = file;
   else          *(void**)v = nullptr;
}

void file_v_Release( FsrFile* file_)
{
   file_t* file = file_vcast( file_);
   if (-- file->open_count == 0) file_release( file);
}

FsrList* /*list*/ file_v_ListFactory( FsrFile* file_)
{
   file_t* file = file_vcast( file_);
   return &lister_factory( file)->vtbl;
}

void file_v_Control( FsrFile* file_, FsrControlOp* op, void* unused1)
{
   op->complete( op, fsrErrorInvalid);
}

void file_v_MediaInfo( FsrFile* file_, FsrMediaInfoOp* op, void* unused1)
{
   op->complete( op, 0);
}

void file_v_Capacity( FsrFile* file_, FsrCapacityOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   volume_t* volume = file->volume;
   op->result.totalCapacity = volume_max_capacity;
   op->result.availableCapacity = volume_max_capacity - volume->capacity;
   op->complete( op, 0);
}

void file_v_Flush( FsrFile* file_, FsrFlushOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   if (op->fileFlags != fsrFileFlagsInvalid)
   {
      file->file_flags = op->fileFlags;
   }
   if (op->color != fsrColorInvalid)
   {
      file->color = op->color;
   }
   if (op->createTime != fsrTimeInvalid)
   {
      file->create_time = op->createTime;
   }
   if (op->writeTime != fsrTimeInvalid)
   {
      file->write_time = op->writeTime;
   }
   if (file->file_type == fsrFileTypeSymlink && op->linkData && (op->linkDataSize || file->file_size))
   {
      file_set_data_size( file, 0);
      file_write_data( file, 0/*off*/, op->linkData, 0, op->linkDataSize, nullptr/*actualSize*/);
   }
   file_get_open_attribs( file, &op->result.openAttribs);
   op->complete( op, 0);
}

void file_v_Access( FsrFile* file_, FsrAccessOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   file_get_open_attribs( file, &op->result.openAttribs);
   op->complete( op, 0);
}

void file_v_Replace( FsrFile* file_, FsrReplaceOp* op, void* unused1)
{
   file_t* target_file = file_vcast( file_);
   // volume_t* volume = target_file->volume;
   file_t* file = nullptr;
   int perr;
   name_t* target_name;
   perr = file_find_name( target_file, &target_name, op->targetParentFileId, op->targetEndName);
   if (perr) goto done;
      // Prevent delete of non-empty folders.
   perr = name_check_folder_delete( target_name);
   if (perr) goto done;
   file = file_dyna_retain( op->newCreatedFile);
   file_init( file, target_file->file_type, op->createFileFlags, op->writeTime);
   file_get_open_attribs( file, &op->result.openAttribs);
   name_factory( file, target_name->parent, target_name->end_name, op->writeTime);
   name_delete( target_name, op->writeTime);
done:
   op->complete( op, perr);
   if (file) file_release( file);
}

void file_v_Move( FsrFile* file_, FsrMoveOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   volume_t* volume = file->volume;
   int perr;
   name_t* source_name = nullptr;
   name_t* target_name;
   file_t* target_parent;
   file_t* target_file;
   if (op->deleteSource)
   {
      perr = file_find_name( file, &source_name, op->sourceParentFileId, op->sourceEndName);
      if (perr) goto done;
   }
   perr = volume_find_name( volume, &target_name, &target_parent, &op->result.linkNamePartCount, op->targetNameParts, op->targetNamePartCount);
   if (perr) goto done;
      // Watch for and allow case change rename.
   if (op->deleteSource && target_name == source_name)
   {
      target_parent = target_name->parent;
      target_name = nullptr;
   }
   else
   {
         // Prevent move/link that would create loop in namespace.
      perr = file_check_move_loop( file, target_parent);
      if (perr) goto done;
   }
   if (target_name)
   {
      target_file = target_name->file;
      op->result.targetExistedFile = file_open_retain( target_file);
      file_get_open_attribs( target_file, &op->result.openAttribs);
      op->result.parentFileId = target_name->parent->file_id;
      op->result.endName = target_name->end_name;
      if (target_file->file_type == fsrFileTypeSymlink && target_file->file_size && target_file->file_size <= file_data_block_size)
      {
         op->result.linkData = target_file->data_block_list[0];
         op->result.linkDataSize = target_file->file_size;
      }
      goto done;
   }
   name_t* name = name_factory( file, target_parent, op->targetNameParts[ op->targetNamePartCount - 1], op->writeTime);
   if (op->deleteSource)
   {
      name_delete( source_name, op->writeTime);
   }
   file_get_open_attribs( file, &op->result.openAttribs);
   op->result.parentFileId = name->parent->file_id;
   op->result.endName = name->end_name;
done:
   op->complete( op, perr);
}

void file_v_MoveReplace( FsrFile* file_, FsrMoveReplaceOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   int perr;
   name_t* source_name;
   name_t* target_name;
   file_t* target_file = file_dcast( op->targetFile);
   if (!target_file)
   {
      perr = fsrErrorInvalid;
      goto done;
   }
   perr = file_find_name( file, &source_name, op->sourceParentFileId, op->sourceEndName);
   if (perr) goto done;
   perr = file_find_name( target_file, &target_name, op->targetParentFileId, op->targetEndName);
   if (perr) goto done;
      // Prevent move/link that would create loop in namespace.
   perr = file_check_move_loop( file, target_name->parent);
   if (perr) goto done;
      // Root file replace not supported, but can not happen since
      // root has no name.
   ASSERT( target_file != file->volume->root);
   name_factory( file, target_name->parent, target_name->end_name, op->writeTime);
   if (perr) goto done;
   name_delete( target_name, op->writeTime);
   if (op->deleteSource)
   {
      name_delete( source_name, op->writeTime);
   }
done:
   op->complete( op, perr);
}

void file_v_Delete( FsrFile* file_, FsrDeleteOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   name_t* name;
   int perr = file_find_name( file, &name, op->parentFileId, op->endName);
   if (perr) goto done;
      // Prevent delete of non-empty folders.
   perr = name_check_folder_delete( name);
   if (perr) goto done;
   name_delete( name, op->writeTime);
done:
   op->complete( op, perr);
}

void file_v_Read( FsrFile* file_, FsrReadOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   file_read_data( file, op->fileOffset, op->buf, op->bufOff, op->requestedSize, &op->result.actualSize);
   op->complete( op, 0);
}

void file_v_Write( FsrFile* file_, FsrWriteOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   int perr = file_write_data( file, op->fileOffset, op->buf, op->bufOff, op->requestedSize, &op->result.actualSize);
   op->complete( op, perr);
}

void file_v_SetSize( FsrFile* file_, FsrSetSizeOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   int perr = file_set_data_size( file, op->fileSize);
   op->complete( op, perr);
}

void file_v_ReadXattr( FsrFile* file_, FsrReadXattrOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   int perr = 0;
   if (op->name && strcmp( op->name, "") != 0)
   {
      perr = fsrErrorUnsupported;
      goto done;
   }
   op->result.xattrSize = file->resource_size;
   if (op->offset < file->resource_size)
   {
      op->result.actualSize = file->resource_size - op->offset;
   }
   if (op->result.actualSize > op->requestedSize)
   {
      op->result.actualSize = op->requestedSize;
   }
   pt_iobufv_dest_copy( op->buf, 0, file->resource_data + op->offset, op->result.actualSize);
done:
   op->complete( op, perr);
}

void file_v_WriteXattr( FsrFile* file_, FsrWriteXattrOp* op, void* unused1)
{
   file_t* file = file_vcast( file_);
   int perr = 0;
   uint8_t* new_resource_data;
   if (op->name && strcmp( op->name, "") != 0)
   {
      perr = fsrErrorUnsupported;
      goto done;
   }
   if (op->xattrSize > file_max_resource_size)
   {
      perr = fsrErrorNoSpace;
      goto done;
   }
   if (!op->xattrSize)
   {
      free( file->resource_data);
      file->resource_data = nullptr;
      file->resource_size = 0;
      goto done;
   }
   if (op->xattrSize > file->resource_size)
   {
      new_resource_data = calloc( 1, op->xattrSize);
      if (!new_resource_data)
      {
         perr = fsrErrorNoSpace;
         goto done;
      }
      if (file->resource_data) memcpy( new_resource_data, file->resource_data, file->resource_size);
      free( file->resource_data);
      file->resource_data = new_resource_data;
   }
   file->resource_size = op->xattrSize;
   op->result.actualSize = op->xattrSize - op->offset;
   if (op->result.actualSize > op->requestedSize)
   {
      op->result.actualSize = op->requestedSize;
   }
   pt_iobufv_src_copy( op->buf, 0, file->resource_data + op->offset, op->result.actualSize);
done:
   op->complete( op, perr);
}

FsrFile_vtbl_t file_vtbl = {
   .DynaCast    = file_v_DynaCast   ,
   .Release     = file_v_Release    ,
   .ListFactory = file_v_ListFactory,
   .Control     = file_v_Control    ,
   .MediaInfo   = file_v_MediaInfo  ,
   .Capacity    = file_v_Capacity   ,
   .Flush       = file_v_Flush      ,
   .Access      = file_v_Access     ,
   .Move        = file_v_Move       ,
   .MoveReplace = file_v_MoveReplace,
   .Delete      = file_v_Delete     ,
   .Read        = file_v_Read       ,
   .Write       = file_v_Write      ,
   .SetSize     = file_v_SetSize    ,
   .ReadXattr   = file_v_ReadXattr  ,
   .WriteXattr  = file_v_WriteXattr ,
   };

file_t* file_factory1( volume_t* volume)
{
   file_t* file = calloc( 1, sizeof( file_t) + volume->file_extension_size);
   file->vtbl = &file_vtbl;
   volume_retain( volume);
   file->volume = volume;
   file->ref_count = 1;
   return file;
}

file_t* file_factory( volume_t* volume, uint8_t file_type, uint8_t file_flags, int64_t write_time)
{
   file_t* file = file_factory1( volume);
   file_init( file, file_type, file_flags, write_time);
   return file;
}

void volume_v_DynaCast( FsrFormatter* volume_, void* v, const char* type_id)
{
   *(void**)v = nullptr;
}

void volume_v_Release( FsrFormatter* volume_)
{
   volume_t* volume = volume_vcast( volume_);
   file_t* root = volume->root;
   if (root)
   {
      file_delete_children( root);
      volume->root = nullptr;
      file_release( root);
   }
   volume_release( volume);
}

void volume_v_Cancel( FsrFormatter* volume_)
{
}

void volume_v_Configure( FsrFormatter* volume_, FsrFormatterConfig* config)
{
   volume_t* volume = volume_vcast( volume_);
   volume->file_extension_size = config->fileExtensionSize;
   volume->lister_extension_size = config->listExtensionSize;
   if (volume->root)
   {
      file_release( volume->root);
      volume->root = nullptr;
   }
   volume->root = file_factory( volume, fsrFileTypeFolder, 0/*file_flags*/, 0/*write_time*/);
}

int/*perr*/ volume_v_FileFactory( FsrFormatter* volume_, FsrFile** out_file)
{
   volume_t* volume = volume_vcast( volume_);
   file_t* file = file_factory1( volume);
   *out_file = file_open_retain( file);
   file_release( file);
   return 0;
}

void volume_v_Control( FsrFormatter* volume_, FsrControlOp* op, void* unused1)
{
   op->complete( op, fsrErrorInvalid);
}

void volume_v_MediaInfo( FsrFormatter* volume_, FsrMediaInfoOp* op, void* unused1)
{
   op->complete( op, 0);
}

void volume_v_Capacity( FsrFormatter* volume_, FsrCapacityOp* op, void* unused1)
{
   volume_t* volume = volume_vcast( volume_);
   op->result.totalCapacity = volume_max_capacity;
   op->result.availableCapacity = volume_max_capacity - volume->capacity;
   op->complete( op, 0);
}

void volume_v_FlushMedia( FsrFormatter* volume_, FsrFlushMediaOp* op, void* unused1)
{
   op->result.msecFlushDelay = -1;
   op->complete( op, 0);
}

void volume_v_ReadTouchMap( FsrFormatter* volume_, FsrReadTouchMapOp* op, void* unused1)
{
   op->complete( op, fsrErrorUnsupported);
}

void volume_v_Open( FsrFormatter* volume_, FsrOpenOp* op, void* unused1)
{
   volume_t* volume = volume_vcast( volume_);
   int perr = 0;
   file_t* file = nullptr;
   name_t* name = nullptr;
   file_t* parent;
   if (!op->namePartCount)
   {
         // Special case open of root. Not handled in normal
         // case since root has no name.
      op->result.existedFile = file_open_retain( volume->root);
      file_get_open_attribs( volume->root, &op->result.openAttribs);
      op->result.parentFileId = volume->root->file_id;
      goto done;
   }
   perr = volume_find_name( volume, &name, &parent, &op->result.linkNamePartCount, op->nameParts, op->namePartCount);
   if (perr) goto done;
   if (name)
   {
      file = name->file;
      file_retain( file);
      op->result.existedFile = file_open_retain( file);
      file_get_open_attribs( file, &op->result.openAttribs);
      if (file->file_type == fsrFileTypeSymlink && file->file_size && file->file_size <= file_data_block_size)
      {
         op->result.linkData = file->data_block_list[0];
         op->result.linkDataSize = scast( size_t, file->file_size);
      }
      op->result.parentFileId = name->parent->file_id;
      op->result.endName = name->end_name;
      goto done;
   }
   if (op->createFileType == fsrFileTypeNone)
   {
      perr = fsrErrorNotFound;
      goto done;
   }
   file = file_dyna_retain( op->newCreatedFile);
   file_init( file, op->createFileType, op->createFileFlags, op->writeTime);
   file_get_open_attribs( file, &op->result.openAttribs);
   name = name_factory( file, parent, op->nameParts[ op->namePartCount - 1], op->writeTime);
   op->result.parentFileId = parent->file_id;
   op->result.endName = name->end_name;
done:
   op->complete( op, perr);
   if (file) file_release( file);
}

FsrFormatter_vtbl_t volume_vtbl = {
   .DynaCast     = volume_v_DynaCast    ,
   .Release      = volume_v_Release     ,
   .Cancel       = volume_v_Cancel      ,
   .Configure    = volume_v_Configure   ,
   .FileFactory  = volume_v_FileFactory ,
   .Control      = volume_v_Control     ,
   .MediaInfo    = volume_v_MediaInfo   ,
   .Capacity     = volume_v_Capacity    ,
   .FlushMedia   = volume_v_FlushMedia  ,
   .ReadTouchMap = volume_v_ReadTouchMap,
   .Open         = volume_v_Open        ,
   };

volume_t* volume_factory( void)
{
   volume_t* volume = calloc( 1, sizeof( volume_t));
   volume->vtbl = &volume_vtbl;
   volume->ref_count = 1;
   return volume;
}

int main( int argc, const char*const* argv)
{
   int err = 0;
   PT_FD_T transport_fd = FD_INVALID;
   PT_FD_T mount_fd = FD_INVALID;
   FsrMountCreateParams mcp; FsrMountCreateParams_Init( &mcp);
   FsrServer* server = nullptr;
   volume_t* volume = volume_factory();

   if (argc <= 1)
   {
      printf(
         "Sample file system application.\n"
         "syntax: tempfs <mount name>\n");
      err = -1;
      goto done;
   }
   mcp.mountSourceName = argv[1];
   if (!FsrApiLoad())
   {
      err = 999;
      printf( "ERROR: Unable to open FSR api, verify fsrapi DLL is present.\n");
      goto done;
   }
      // Communication between the driver and file system is done
      // over a socket.
   err = create_socket( &transport_fd, &mcp.transport);
   if (err) goto done;
   err = FsrMountCreate( &mount_fd, nullptr, &mcp);
   if (err)
   {
      printf( "ERROR: %d Unable to create mount, verify FSR is installed.\n", err);
      goto done;
   }
      // Close driver end pipe handles now. Driver has duplicated what
      // it needs. If these handles are not closed then pipes will not
      // break if driver disconnects, leaving us stuck in the
      // marshaller.
   close_fd( mcp.transport);
   mcp.transport = FD_INVALID;
   err = FsrServerFactory( &server, transport_fd, nullptr/*transport_security*/, stdout_fd(), "hellofs", "hellofs", 0/*volume_flags*/);
   if (err) goto done;
   err = (*server)->Connect( server, nullptr/*cancel_pin*/, nullptr/*client_version*/, nullptr/*client_flags*/, nullptr/*client_file_name_type*/, &volume->vtbl);
   if (err)
   {
      printf( "ERROR: %d Unable to connect.\n", err);
      goto done;
   }
      // The server uses alertable I/O, so process can be exited via ctrl+c.
   printf( "Press CTRL+C to exit.\n");
   (*server)->Serve( server, nullptr/*cancel_pin*/, nullptr, &volume->vtbl);

done:
   close_fd( mcp.transport);
   close_fd( transport_fd);
   close_fd( mount_fd);
   if (server) (*server)->Release( server);
   volume_release( volume);
   FsrApiUnload();
   return err;
}
