s390utils/0050-cmsfs-fuse-support-for...

6057 lines
141 KiB
Diff

From d7e1d7b005747e4ff08db77ab0eaade80e63636a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan@danny.cz>
Date: Fri, 28 Jan 2011 13:19:48 +0100
Subject: [PATCH 50/61] cmsfs-fuse: support for CMS EDF filesystems via fuse
Summary: cmsfs-fuse: support for CMS EDF filesystems via fuse
Description: Use the cmsfs-fuse command to read and write files stored on a z/VM
CMS disk. The cmsfs-fuse file system translates the record-based EDF file
system on the CMS disk to UNIX semantics. It is possible to mount a CMS
disk and use common Linux tools to access the files on the disk.
---
Makefile | 2 +-
README | 12 +
cmsfs-fuse/Makefile | 31 +
cmsfs-fuse/amap.c | 217 ++
cmsfs-fuse/cmsfs-fuse.1 | 206 ++
cmsfs-fuse/cmsfs-fuse.c | 4536 +++++++++++++++++++++++++++++++++++++++++
cmsfs-fuse/cmsfs-fuse.h | 134 ++
cmsfs-fuse/config.c | 122 ++
cmsfs-fuse/dasd.c | 224 ++
cmsfs-fuse/ebcdic.h | 153 ++
cmsfs-fuse/edf.h | 123 ++
cmsfs-fuse/etc/filetypes.conf | 107 +
cmsfs-fuse/helper.h | 54 +
13 files changed, 5920 insertions(+), 1 deletions(-)
create mode 100644 cmsfs-fuse/Makefile
create mode 100644 cmsfs-fuse/amap.c
create mode 100644 cmsfs-fuse/cmsfs-fuse.1
create mode 100644 cmsfs-fuse/cmsfs-fuse.c
create mode 100644 cmsfs-fuse/cmsfs-fuse.h
create mode 100644 cmsfs-fuse/config.c
create mode 100644 cmsfs-fuse/dasd.c
create mode 100644 cmsfs-fuse/ebcdic.h
create mode 100644 cmsfs-fuse/edf.h
create mode 100644 cmsfs-fuse/etc/filetypes.conf
create mode 100644 cmsfs-fuse/helper.h
diff --git a/Makefile b/Makefile
index 89c5fc5..e1f6f83 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ LIB_DIRS = libvtoc libu2s
SUB_DIRS = $(LIB_DIRS) zipl zdump fdasd dasdfmt dasdview tunedasd \
tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \
vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
- ziomon iucvterm hyptop
+ ziomon iucvterm hyptop cmsfs-fuse
all: subdirs_make
diff --git a/README b/README
index ffd5e54..4335b43 100644
--- a/README
+++ b/README
@@ -157,6 +157,11 @@ s390-tools (1.8.2)
- ts-shell: Terminal server shell to authorize and control IUCV terminal
connections for individual Linux users.
+ * cmsfs-fuse:
+ Use the cmsfs-fuse command to read and write files stored on a z/VM
+ CMS disk. The cmsfs-fuse file system translates the record-based EDF file
+ system on the CMS disk to UNIX semantics. It is possible to mount a CMS
+ disk and use common Linux tools to access the files on the disk.
For more information refer to the following publications:
* "Device Drivers, Features, and Commands" chapter "Useful Linux commands"
@@ -179,6 +184,13 @@ Dependencies:
For executing the ziomon tools an installed blktrace package is required.
See: git://git.kernel.dk/blktrace.git
+ * cmsfs-fuse:
+ cmsfs-fuse depends on FUSE. FUSE is provided by installing the fuse and
+ libfuse packages and by a kernel compiled with CONFIG_FUSE_FS.
+ For compiling the s390-tools package the fuse-devel package is required.
+ For further information about FUSE see: http://fuse.sourceforge.net/
+ cmsfs-fuse requires FUSE version 2.8.1 or newer for full functionality.
+
Release History:
================
1.8.2
diff --git a/cmsfs-fuse/Makefile b/cmsfs-fuse/Makefile
new file mode 100644
index 0000000..7df81e0
--- /dev/null
+++ b/cmsfs-fuse/Makefile
@@ -0,0 +1,31 @@
+#!/usr/bin/make -f
+
+include ../common.mak
+
+CPPFLAGS += -I../include
+
+all: cmsfs-fuse
+
+CFLAGS += -D_FILE_OFFSET_BITS=64 -DHAVE_SETXATTR -I/usr/include/fuse
+LDLIBS += -lfuse -lpthread -lrt -ldl -lm
+
+OBJECTS = cmsfs-fuse.o dasd.o amap.o config.o
+$(OBJECTS): *.h Makefile
+
+CMSFS_FUSE_DIR = $(SYSCONFDIR)/cmsfs-fuse
+CONFIG_FILES = filetypes.conf
+
+cmsfs-fuse: $(OBJECTS)
+
+install: all
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 cmsfs-fuse $(USRBINDIR)
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 cmsfs-fuse.1 $(MANDIR)/man1
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -d $(CMSFS_FUSE_DIR)
+ for cnf in $(CONFIG_FILES); do \
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 etc/$$cnf $(CMSFS_FUSE_DIR) ; \
+ done
+
+clean:
+ rm -f cmsfs-fuse *.o
+
+.PHONY: all install clean
diff --git a/cmsfs-fuse/amap.c b/cmsfs-fuse/amap.c
new file mode 100644
index 0000000..04f83fc
--- /dev/null
+++ b/cmsfs-fuse/amap.c
@@ -0,0 +1,217 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * Allocation map functions.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include "zt_common.h"
+#include "helper.h"
+#include "edf.h"
+#include "cmsfs-fuse.h"
+
+/*
+ * Get block number from address.
+ */
+static int amap_blocknumber(off_t addr)
+{
+ return addr / BYTES_PER_BLOCK;
+}
+
+/*
+ * Get the block number for a specific level.
+ */
+static int amap_blocknumber_level(int level, off_t addr)
+{
+ int entry = amap_blocknumber(addr);
+
+ while (level-- > 1)
+ entry /= PTRS_PER_BLOCK;
+ return entry;
+}
+
+/*
+ * Return address of to the allocation map for a block number > 0.
+ */
+static off_t get_amap_addr(int level, off_t addr, off_t ptr)
+{
+ int block = amap_blocknumber_level(level, addr);
+
+ if (cmsfs.amap_levels == 0)
+ return cmsfs.amap;
+
+ if (level--) {
+ ptr = get_fixed_pointer(ptr + block * PTR_SIZE);
+ if (!ptr)
+ DIE("amap invalid ptr at addr: %lx\n",
+ ptr + block * PTR_SIZE);
+ return get_amap_addr(level, addr, ptr);
+ }
+ return ptr;
+}
+
+/*
+ * Mark disk address as allocated in alloc map. Unaligned addr is tolerated.
+ */
+static void amap_block_set(off_t addr)
+{
+ off_t amap = get_amap_addr(cmsfs.amap_levels, addr, cmsfs.amap);
+ int rc, block = amap_blocknumber(addr);
+ unsigned int byte, bit;
+ u8 entry;
+
+ if (block > 0)
+ addr -= block * BYTES_PER_BLOCK;
+
+ addr >>= BITS_PER_DATA_BLOCK;
+ byte = addr / 8;
+ bit = addr % 8;
+
+ rc = _read(&entry, sizeof(entry), amap + byte);
+ BUG(rc < 0);
+
+ /* already used */
+ BUG(entry & (1 << (7 - bit)));
+
+ entry |= (1 << (7 - bit));
+ rc = _write(&entry, sizeof(entry), amap + byte);
+ BUG(rc < 0);
+}
+
+/*
+ * Mark disk address as free in alloc map. Unaligned addr is tolerated.
+ */
+static void amap_block_clear(off_t addr)
+{
+ off_t amap = get_amap_addr(cmsfs.amap_levels, addr, cmsfs.amap);
+ int rc, block = amap_blocknumber(addr);
+ unsigned int byte, bit;
+ u8 entry;
+
+ if (block > 0)
+ addr -= block * BYTES_PER_BLOCK;
+
+ addr >>= BITS_PER_DATA_BLOCK;
+ byte = addr / 8;
+ bit = addr % 8;
+
+ rc = _read(&entry, sizeof(entry), amap + byte);
+ BUG(rc < 0);
+
+ /* already cleared */
+ BUG(!(entry & (1 << (7 - bit))));
+
+ entry &= ~(1 << (7 - bit));
+ rc = _write(&entry, sizeof(entry), amap + byte);
+ BUG(rc < 0);
+}
+
+/*
+ * Return the first free bit in one byte.
+ */
+static int find_first_empty_bit(u8 entry)
+{
+ u8 i;
+
+ for (i = 0; i < 8; i++)
+ if (!(entry & 1 << (7 - i)))
+ return i;
+ /* unreachable */
+ return -1;
+}
+
+/*
+ * Look for the first unallocated block and return addr of allocated block.
+ */
+static off_t __get_free_block(int level, off_t amap, int block_nr)
+{
+ off_t ptr, addr = amap;
+ unsigned int bit;
+ int left, rc, i;
+ u8 entry;
+
+ if (level > 0) {
+ level--;
+ left = PTRS_PER_BLOCK;
+ while (left--) {
+ ptr = get_fixed_pointer(addr);
+ if (!ptr)
+ return 0;
+ ptr = __get_free_block(level, ptr, block_nr);
+ if (ptr)
+ return ptr;
+ addr += PTR_SIZE;
+ block_nr++;
+ }
+ return 0;
+ }
+
+ for (i = 0; i < cmsfs.blksize; i++) {
+ rc = _read(&entry, sizeof(entry), amap + i);
+ BUG(rc < 0);
+
+ if (entry != 0xff) {
+ /* get first empty bit and add to addr */
+ bit = find_first_empty_bit(entry);
+
+ /* bit -> addr */
+ addr = (i * cmsfs.blksize * 8 + cmsfs.blksize * bit)
+ + (block_nr * BYTES_PER_BLOCK);
+ amap_block_set(addr);
+ return addr;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Allocate a free block and increment label block counter.
+ */
+off_t get_free_block(void)
+{
+ off_t addr;
+
+ if (cmsfs.used_blocks + cmsfs.reserved_blocks >= cmsfs.total_blocks)
+ return -ENOSPC;
+ addr = __get_free_block(cmsfs.amap_levels, cmsfs.amap, 0);
+ BUG(!addr);
+
+ cmsfs.used_blocks++;
+ return addr;
+}
+
+/*
+ * Allocate a zero-filled block and increment label block counter.
+ */
+off_t get_zero_block(void)
+{
+ off_t addr = get_free_block();
+ int rc;
+
+ if (addr < 0)
+ return -ENOSPC;
+
+ rc = _zero(addr, cmsfs.blksize);
+ if (rc < 0)
+ return rc;
+ return addr;
+}
+
+/*
+ * Free a block and decrement label block counter.
+ */
+void free_block(off_t addr)
+{
+ if (addr) {
+ amap_block_clear(addr);
+ cmsfs.used_blocks--;
+ }
+}
diff --git a/cmsfs-fuse/cmsfs-fuse.1 b/cmsfs-fuse/cmsfs-fuse.1
new file mode 100644
index 0000000..2dc825d
--- /dev/null
+++ b/cmsfs-fuse/cmsfs-fuse.1
@@ -0,0 +1,206 @@
+.\" Copyright 2010 Jan Glauber (jan.glauber@de.ibm.com)
+.\"
+.TH CMSFS-FUSE 1 "February 2010" "s390-tools"
+
+.SH NAME
+cmsfs-fuse \- File system for z/VM CMS disks
+
+.SH SYNOPSIS
+.SS mounting:
+.TP
+\fBcmsfs-fuse\fP DEVICE MOUNTPOINT [OPTIONS]
+.SS unmounting:
+.TP
+\fBfusermount\fP -u MOUNTPOINT
+
+.SH DESCRIPTION
+Use the \fBcmsfs-fuse\fP command to provide read and write access
+to files stored on a z/VM CMS disk.
+The cmsfs-fuse file system translates the record-based EDF file system on
+the CMS disk to UNIX semantics.
+After mounting the CMS disk, you can use common Linux tools to access
+the files on the disk. You can enable automatic conversions of text files from
+EBCDIC to ASCII.
+
+Attention: You can inadvertently damage files and lose data when directly
+writing to files within the cmsfs-fuse file system. To avoid problems when writing,
+multiple restrictions must be observed, especially with regard to linefeeds (see
+section RESTRICTIONS).
+
+If you are unsure about how to safely write to a file on the cmsfs-fuse file
+system, copy the file to a location outside the cmsfs-fuse file system, edit the file,
+and then copy it back to its original location.
+
+.SH OPTIONS
+.SS "general options:"
+.TP
+\fB\-o\fR opt,[opt...]
+Fuse or mount command options. For fuse options see below, for mount options
+see \fBmount(8)\fP.
+.TP
+\fB\-h\fR or \fB\-\-help\fR
+Print usage information, then exit.
+.TP
+\fB\-v\fR or \fB\-\-version\fR
+Print version information, then exit.
+.SS "cmsfs-fuse options:"
+.TP
+\fB\-a\fR or \fB\-\-ascii\fR
+Interpret all files on the CMS disk as text files and convert them from
+EBCDIC to ASCII.
+.TP
+\fB--from\fR
+The codepage of the files on the CMS disk. If this option is not
+specified the default codepage CP1047 is used. For a list of all available
+codepages see iconv --list.
+.TP
+\fB--to\fR
+The codepage to which CMS files should be converted to. If this option is not
+specified the default codepage ISO-8859-1 is used. For a list of all available
+codepages see iconv --list.
+.TP
+\fB\-t\fR or \fB\-\-filetype\fR
+Interpret files on the CMS disk as text files based on the file type
+and convert them from EBCDIC to ASCII. The file types that are treated
+as text files are taken from a configuration file (see section CONFIGURATION FILES).
+
+.SS "Applicable FUSE options (version 2.8):"
+.TP
+\fB\-d\fR or \fB\-o\fR debug
+Enable debug output (implies \fB\-f\fR)
+.TP
+\fB\-f\fR
+Foreground operation
+.TP
+\fB\-o\fR allow_other
+Allow access by other users
+.TP
+\fB\-o\fR allow_root
+Allow access by root
+.TP
+\fB\-o\fR nonempty
+Allow mounts over non\-empty file/dir
+.TP
+\fB\-o\fR default_permissions
+Enable permission checking by kernel
+.TP
+.TP
+\fB\-o\fR max_read=N
+Set maximum size of read requests
+.TP
+\fB\-o\fR kernel_cache
+Cache files in kernel
+.TP
+\fB\-o\fR [no]auto_cache
+Enable caching based on modification times
+.TP
+\fB\-o\fR umask=M
+Set file permissions (octal)
+.TP
+\fB\-o\fR uid=N
+Set file owner
+.TP
+\fB\-o\fR gid=N
+Set file group
+.TP
+\fB\-o\fR max_write=N
+Set maximum size of write requests
+.TP
+\fB\-o\fR max_readahead=N
+Set maximum readahead
+.TP
+\fB\-o\fR async_read
+Perform reads asynchronously (default)
+.TP
+\fB\-o\fR sync_read
+Perform reads synchronously
+.TP
+\fB\-o big_writes\fR
+Enable write operations with more than 4 KB
+
+.SH EXTENDED ATTRIBUTES
+Use the following extended attributes to handle the CMS characteristics of a file:
+
+\fBuser.record_format\fR: The format of a file. Allowed values are F for fixed record length files
+and V for variable record length files. This attribute can be set only if the file is empty.
+
+\fBuser.record_lrecl\fR: The record length of a file. This attribute can be set only for a fixed
+record length file and if the file is empty. A valid record length is an integer in the range 1-65535.
+
+\fBuser.file_mode\fR: The file mode of a file which is interpreted by CMS. The file mode consists
+of a mode letter from A-Z and mode number from 0-6.
+
+New files are created by default as variable files with file mode A1.
+
+.SH RESTRICTIONS
+\fBrename\fR and \fBcreat\fR:
+Uppercase file names are enforced.
+
+\fBtruncate\fR:
+Only shrinking of a file is supported. For fixed length record files, the new file size must
+be a multiple of the record length.
+
+\fBunlink\fR:
+Creating a file with the name of a previously unlinked file which is still in use is not supported
+and will fail with -ENOENT.
+
+\fBwrite\fR:
+Writes are supported only at the end of the file.
+A write on a fixed length record file always writes a multiple
+of the record length. If additional bytes are added, the
+bytes are filled with zero in binary mode or with spaces in ASCII mode. Sparse files are not supported.
+If the cp tool is used to write files to a CMS disk the option "--sparse=never" must be specified.
+
+If ASCII translation is enabled for a file a linefeed character determines the end of a record.
+The following restrictions must be observed for writing files in ASCII mode:
+For fixed record length files a linefeed must occur exactly after a record of the length specified in the fixed record length.
+For variable record length files a linefeed must occur after the maximum record length is reached or earlier.
+If a record of a variable record length file consists only of a linefeed character cmsfs-fuse adds a space to this record since
+empty records are not supported by the CMS file system.
+
+.SH CONFIGURATION FILES
+cmsfs-fuse uses a configuration file for automatic translation based on the file type.
+Upon startup, cmsfs-fuse evaluates the file .cmsfs-fuse/filetypes.conf in the user's home directory. If the file does not
+exist cmsfs-fuse evaluates the file /etc/cmsfs-fuse/filetypes.conf.
+
+The filetypes.conf file contains the CMS file types that are automaticaly translated to ASCII if cmsfs-fuse is started
+with the -t option. The syntax of the configuration file is one file type per line. Lines that start with a # followed by a space are treated as
+comments and are ignored. The file type is 8 characters long and must consist of valid CMS file name characters only.
+
+The default file types in the configuration file were taken from the z/VM TCPIP.DATA file
+(z/VM version 5.4.0).
+
+.SH EXAMPLES
+To mount the CMS disk with the name dasde enter:
+.br
+
+ # cmsfs-fuse /dev/dasde /mnt
+
+.br
+To mount the CMS disk with the name dasde and enable automatic translation
+of known text files enter:
+.br
+
+ # cmsfs-fuse -t /dev/dasde /mnt
+
+To mount the CMS disk with the name dasde and enable automatic translation
+of all files to UTF-8 enter:
+.br
+
+ # cmsfs-fuse --to=UTF-8 -a /dev/dasde /mnt
+
+To unmount the CMS disk mounted on /mnt enter:
+.br
+
+ # fusermount -u /mnt
+
+To show the record format of file PROFILE.EXEC assuming the CMS disk was mounted on /mnt:
+
+ # getfattr -n user.record_format /mnt/PROFILE.EXEC
+
+The following example assumes that an empty, fixed record format file, PROFILE.EXEC, can be accessed on a CMS disk that has been mounted on /mnt. To set the record length of PROFILE.EXEC to 80 bytes:
+
+ # setfattr -n user.record_lrecl -v 80 /mnt/PROFILE.EXEC
+
+.SH SEE ALSO
+attr (5), getfattr (1), setfattr(1), iconv(1) and Linux on System z: Device Drivers, Features and Commands
diff --git a/cmsfs-fuse/cmsfs-fuse.c b/cmsfs-fuse/cmsfs-fuse.c
new file mode 100644
index 0000000..6c5b0b5
--- /dev/null
+++ b/cmsfs-fuse/cmsfs-fuse.c
@@ -0,0 +1,4536 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * Main functions.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#define FUSE_USE_VERSION 26
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stddef.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <linux/fs.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <search.h>
+#include <iconv.h>
+#include <ctype.h>
+#include <math.h>
+#ifdef HAVE_SETXATTR
+#include <linux/xattr.h>
+#endif
+#include <fuse.h>
+#include <fuse_opt.h>
+
+#include "zt_common.h"
+#include "list.h"
+#include "helper.h"
+#include "edf.h"
+#include "cmsfs-fuse.h"
+#include "ebcdic.h"
+
+struct cmsfs cmsfs;
+struct list open_file_list;
+struct list text_type_list;
+FILE *logfile;
+
+#define PAGE_SIZE 0xfff
+#define FSNAME_MAX_LEN 50
+#define MAX_FNAME 18
+
+#define CMSFS_OPT(t, p, v) { t, offsetof(struct cmsfs, p), v }
+
+enum {
+ KEY_HELP,
+ KEY_VERSION,
+};
+
+static const struct fuse_opt cmsfs_opts[] = {
+ CMSFS_OPT("-a", mode, TEXT_MODE),
+ CMSFS_OPT("--ascii", mode, TEXT_MODE),
+ CMSFS_OPT("-t", mode, TYPE_MODE),
+ CMSFS_OPT("--filetype", mode, TYPE_MODE),
+ CMSFS_OPT("--from=%s", codepage_from, 0),
+ CMSFS_OPT("--to=%s", codepage_to, 0),
+
+ FUSE_OPT_KEY("-h", KEY_HELP),
+ FUSE_OPT_KEY("--help", KEY_HELP),
+ FUSE_OPT_KEY("-v", KEY_VERSION),
+ FUSE_OPT_KEY("--version", KEY_VERSION),
+ FUSE_OPT_END
+};
+
+static void usage(const char *progname)
+{
+ fprintf(stderr,
+"Usage: %s DEVICE MOUNTPOINT [OPTIONS]\n"
+"\n"
+"Use the cmsfs-fuse command to read and write files stored on a z/VM CMS disk.\n"
+"\n"
+"General options:\n"
+" -o opt,[opt...] Mount options\n"
+" -h --help Print help, then exit\n"
+" -v --version Print version, then exit\n"
+" -t --filetype ASCII translation based on file type\n"
+" -a --ascii Force ascii translation\n"
+" --from= Codepage used on the CMS disk\n"
+" --to= Codepage used for conversion to Linux\n"
+"\n", progname);
+}
+
+static char CODEPAGE_EDF[] = "CP1047";
+static char CODEPAGE_LINUX[] = "ISO-8859-1";
+
+#define USED_BLOCK_ADDR (cmsfs.blksize * 2 + 32)
+
+#define READDIR_FILE_ENTRY -1
+#define READDIR_END_OF_DIR -2
+#define READDIR_DIR_ENTRY -3
+#define READDIR_MAP_ENTRY -4
+
+#define LINEFEED_OFFSET ((struct record *) -1)
+#define LINEFEED_ASCII 0xa
+#define LINEFEED_EBCDIC 0x25
+#define LINEFEED_NOT_FOUND -1
+#define FILLER_EBCDIC 0x40
+#define FILLER_ASCII 0x20
+
+#define RSS_HEADER_STARTED 0x1
+#define RSS_HEADER_COMPLETE 0x2
+#define RSS_DATA_BLOCK_STARTED 0x4
+#define RSS_DATA_BLOCK_EXT 0x8
+
+#define RWS_HEADER_STARTED 0x1
+#define RWS_HEADER_COMPLETE 0x2
+#define RWS_RECORD_INCOMPLETE 0x4
+#define RWS_RECORD_COMPLETE 0x8
+
+#define BWS_BLOCK_NEW 0x1
+#define BWS_BLOCK_USED 0x2
+
+#define WCACHE_MAX (MAX_RECORD_LEN + 1)
+
+struct block {
+ off_t disk_addr;
+ unsigned int disp;
+ int hi_record_nr;
+};
+
+struct record_ext {
+ /* start addr of the extension */
+ off_t disk_start;
+ /* length of extension in this disk block */
+ int len;
+ /* null block start flag */
+ int null_block_started;
+ /* corresponding disk block number */
+ int block_nr;
+
+ struct record_ext *prev;
+ struct record_ext *next;
+};
+
+struct record {
+ /* length of the complete record */
+ unsigned int total_len;
+ /* offset of first record block on the disk */
+ off_t disk_start;
+ /* bytes in first record block */
+ int first_block_len;
+ /* logical offset, dependent on line feed mode */
+ off_t file_start;
+ /* null block start flag */
+ int null_block_started;
+ /* spanned record extension */
+ struct record_ext *ext;
+ /* corresponding disk block number */
+ int block_nr;
+};
+
+struct file;
+
+struct file_operations {
+ int (*cache_data) (struct file *f, off_t addr, int level, int *block,
+ unsigned int *disp, int *record, size_t *total);
+ int (*write_data) (struct file *f, const char *buf, int len, size_t size,
+ int rlen);
+ int (*delete_pointers) (struct file *f, int level, off_t addr);
+ int (*write_pointers) (struct file *f, int level, off_t dst, int offset);
+};
+
+static struct file_operations fops_fixed;
+static struct file_operations fops_variable;
+
+/*
+ * File object for operations that follow open
+ */
+struct file {
+ /* pointer to the fst entry */
+ struct fst_entry *fst;
+ /* fst address on disk */
+ off_t fst_addr;
+ /* translate mode enabled */
+ int translate;
+ /* linefeed mode enabled */
+ int linefeed;
+ /* list of records */
+ struct record *rlist;
+ /* record scan state machine flag */
+ int record_scan_state;
+ /* next record for sequential reads */
+ int next_record_hint;
+ /* counter for null bytes to detect block start */
+ int null_ctr;
+ /* list of disk blocks */
+ struct block *blist;
+ /* disk address of next byte to write */
+ off_t write_ptr;
+ /* the filesize while the file is opened */
+ ssize_t session_size;
+ /* number of null blocks for fixed files */
+ int nr_null_blocks;
+ /* number of written padding bytes for a fixed file */
+ int pad_bytes;
+ /* old levels value, needed to rewrite pointers */
+ int old_levels;
+ /* record write state for variable headers */
+ struct var_record_state *vrstate;
+ /* path name for open and unlink */
+ char path[MAX_FNAME + 1];
+ /* counter for pseudo null length records */
+ int null_records;
+ /* write cache for text mode */
+ char *wcache;
+ /* used bytes in write cache */
+ int wcache_used;
+ /* commited written bytes to FUSE */
+ int wcache_commited;
+ /* dirty flag for file meta data */
+ int ptr_dirty;
+ /* fops pointers */
+ struct file_operations *fops;
+ /* pointers per block constant */
+ int ptr_per_block;
+ /* open list head */
+ struct list list;
+ /* usage counter for all openers */
+ int use_count;
+ /* usage counter for all writers */
+ int write_count;
+ /* unlink flag */
+ int unlinked;
+};
+
+struct var_record_state {
+ int rlen;
+ int record_state;
+ int block_state;
+};
+
+struct xattr {
+ char name[20];
+ size_t size;
+};
+
+/*
+ * Record format: 'F' (fixed) or 'V' (variable), 1 byte
+ * Record lrecl: 0-65535, 5 bytes
+ * Record mode: [A-Z][0-6], 2 bytes
+ */
+struct xattr xattr_format = { .name = "user.record_format", .size = 1 };
+struct xattr xattr_lrecl = { .name = "user.record_lrecl", .size = 5 };
+struct xattr xattr_mode = { .name = "user.file_mode", .size = 2 };
+
+#define SHOW_UNLINKED 0
+#define HIDE_UNLINKED 1
+
+#define WALK_FLAG_LOOKUP 0x1
+#define WALK_FLAG_READDIR 0x2
+#define WALK_FLAG_LOCATE_EMPTY 0x4
+#define WALK_FLAG_CACHE_DBLOCKS 0x8
+
+struct walk_file {
+ int flag;
+ char *name;
+ char *type;
+ void *buf;
+ off_t addr;
+ fuse_fill_dir_t filler;
+ off_t *dlist;
+ int dlist_used;
+};
+
+/*
+ * Prototypes
+ */
+static struct file *create_file_object(struct fst_entry *fst, int *rc);
+static void destroy_file_object(struct file *f);
+
+static unsigned long dec_to_hex(unsigned long long num)
+{
+ unsigned long res;
+
+ asm volatile("cvb %0,%1" : "=d" (res) : "m" (num));
+ return res & 0xffffffff;
+}
+
+static unsigned int hex_to_dec(unsigned int num)
+{
+ unsigned long long res;
+
+ asm volatile("cvd %1,%0" : "=m" (res) : "d" (num));
+ return res & 0xffffffff;
+}
+
+static void setup_iconv(iconv_t *conv, const char *from, const char *to)
+{
+ *conv = iconv_open(to, from);
+ if (*conv == ((iconv_t) -1))
+ DIE("Could not initialize conversion table %s->%s.\n",
+ from, to);
+}
+
+static inline struct file *get_fobj(struct fuse_file_info *fi)
+{
+ return (struct file *) fi->fh;
+}
+
+int _read(void *buf, size_t size, off_t addr)
+{
+ if (((addr + (off_t) size - 1) & ~DATA_BLOCK_MASK) >
+ (addr & ~DATA_BLOCK_MASK))
+ DIE("read: crossing blocks addr: %lx size: %ld\n",
+ addr, size);
+ if ((addr < cmsfs.fdir) || ((size_t) addr > cmsfs.size))
+ return -EIO;
+
+ memcpy(buf, cmsfs.map + addr, size);
+ return 0;
+}
+
+int _write(const void *buf, size_t size, off_t addr)
+{
+ if (((addr + (off_t) size - 1) & ~DATA_BLOCK_MASK) >
+ (addr & ~DATA_BLOCK_MASK))
+ DIE("write: crossing blocks addr: %x size: %d\n",
+ (int)addr, (int)size);
+
+ if ((addr < (2 * cmsfs.blksize)) || ((size_t) addr > cmsfs.size))
+ return -EIO;
+
+ if (buf == NULL)
+ memset(cmsfs.map + addr, 0, size);
+ else
+ memcpy(cmsfs.map + addr, buf, size);
+ return 0;
+}
+
+int _zero(off_t addr, size_t size)
+{
+ return _write(NULL, size, addr);
+}
+
+off_t get_filled_block(void)
+{
+ off_t addr = get_free_block();
+
+ if (addr < 0)
+ return -ENOSPC;
+
+ memset(cmsfs.map + addr, FILLER_EBCDIC, cmsfs.blksize);
+ return addr;
+}
+
+static int get_fop(off_t addr)
+{
+ struct fst_entry fst;
+ int rc;
+
+ rc = _read(&fst, sizeof(fst), addr);
+ BUG(rc < 0);
+ return ABS(fst.fop);
+}
+
+static int get_levels(off_t addr)
+{
+ struct fst_entry fst;
+ int rc;
+
+ rc = _read(&fst, sizeof(fst), addr);
+ BUG(rc < 0);
+ return fst.levels;
+}
+
+static int get_files_count(off_t addr)
+{
+ struct fst_entry fst;
+ int rc;
+
+ rc = _read(&fst, sizeof(fst), addr);
+ BUG(rc < 0);
+ /* ignore director and allocmap entries */
+ return fst.nr_records - 2;
+}
+
+static int get_order(int shift)
+{
+ int count = 0;
+
+ while (!(shift & 0x1)) {
+ shift >>= 1;
+ count++;
+ }
+ return count;
+}
+
+/*
+ * Read pointer from fixed size pointer block and return
+ * absolute address on disk.
+ */
+off_t get_fixed_pointer(off_t addr)
+{
+ struct fixed_ptr ptr;
+ int rc;
+
+ if (!addr)
+ return NULL_BLOCK;
+ rc = _read(&ptr, sizeof(ptr), addr);
+ if (rc < 0)
+ return -EIO;
+ if (!ptr.next)
+ return NULL_BLOCK;
+ else
+ return ABS((off_t)ptr.next);
+}
+
+/*
+ * Read variable pointer from block and return absolute address on disk
+ * and highest record number.
+ */
+static off_t get_var_pointer(off_t addr, int *max_record,
+ unsigned int *disp)
+{
+ struct var_ptr vptr;
+ off_t ptr = 0;
+ int rc;
+
+ BUG(!addr);
+
+ rc = _read(&vptr, VPTR_SIZE, addr);
+ if (rc < 0)
+ return -EIO;
+ ptr = (off_t) vptr.next;
+
+ *max_record = vptr.hi_record_nr;
+ *disp = vptr.disp;
+
+ if (!ptr) {
+ if (vptr.hi_record_nr)
+ return NULL_BLOCK;
+ else
+ return VAR_FILE_END;
+ } else
+ return ABS(ptr);
+}
+
+int is_edf_char(int c)
+{
+ switch (c) {
+ case 'A' ... 'Z':
+ break;
+ case 'a' ... 'z':
+ break;
+ case '0' ... '9':
+ break;
+ case '#':
+ break;
+ case '@':
+ break;
+ case '+':
+ break;
+ case '$':
+ break;
+ case '-':
+ break;
+ case ':':
+ break;
+ case '_':
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Force conversion to upper case since lower case file names although
+ * valid are not accepted by many CMS tools.
+ */
+static void str_toupper(char *str)
+{
+ int i;
+
+ for (i = 0; i < (int) strlen(str); i++)
+ str[i] = toupper(str[i]);
+}
+
+/*
+ * Set the FST date to the specified date.
+ */
+static void update_fst_date(struct fst_entry *fst, struct tm *tm)
+{
+ unsigned int num;
+ int i;
+
+ if (tm->tm_year >= 100)
+ fst->flag |= FST_FLAG_CENTURY;
+ else
+ fst->flag &= ~FST_FLAG_CENTURY;
+ fst->date[0] = tm->tm_year;
+ fst->date[1] = tm->tm_mon + 1;
+ fst->date[2] = tm->tm_mday;
+ fst->date[3] = tm->tm_hour + 1;
+ fst->date[4] = tm->tm_min;
+ fst->date[5] = tm->tm_sec;
+
+ /* convert hex to decimal */
+ for (i = 0; i < 6; i++) {
+ num = fst->date[i];
+ num = hex_to_dec(num);
+ fst->date[i] = num >> 4;
+ }
+}
+
+/*
+ * Set the FST date to the current date.
+ */
+static int set_fst_date_current(struct fst_entry *fst)
+{
+ struct timeval tv;
+ struct tm tm;
+
+ /* convert timespec to tm */
+ memset(&tm, 0, sizeof(struct tm));
+
+ if (gettimeofday(&tv, NULL) < 0) {
+ perror(COMP "gettimeofday failed");
+ return -EINVAL;
+ }
+
+ if (localtime_r(&tv.tv_sec, &tm) == NULL)
+ return -EINVAL;
+
+ update_fst_date(fst, &tm);
+ return 0;
+}
+
+/*
+ * Check if the file is on the opened list.
+ */
+static struct file *file_open(const char *name)
+{
+ char uc_name[MAX_FNAME];
+ struct file *f;
+
+ strncpy(uc_name, name, MAX_FNAME);
+ str_toupper(uc_name);
+
+ list_iterate(f, &open_file_list, list)
+ if (strncmp(f->path + 1, uc_name, MAX_FNAME) == 0)
+ return f;
+ return NULL;
+}
+
+/*
+ * Check if the file is open and unlinked.
+ */
+static int file_unlinked(const char *name)
+{
+ struct file *f = file_open(name);
+
+ if (f && f->unlinked)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Convert EDF date to time_t.
+ */
+static time_t fst_date_to_time_t(char *date, int century)
+{
+ unsigned long long num;
+ unsigned int res[6];
+ struct tm tm;
+ time_t time;
+ int i;
+
+ /*
+ * date : YY MM DD HH MM SS (decimal!)
+ * century: 0=19, 1=20, dead=21
+ * convert decimal to hex
+ */
+ for (i = 0; i < 6; i++) {
+ num = date[i];
+ num <<= 4;
+ num += 0xc; /* plus */
+ res[i] = dec_to_hex(num);
+ }
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = res[0];
+ tm.tm_mon = res[1];
+ tm.tm_mday = res[2];
+ tm.tm_hour = res[3];
+ tm.tm_min = res[4];
+ tm.tm_sec = res[5];
+ /* see man 3 tzset */
+ tm.tm_isdst = daylight;
+
+ /* prepare for mktime */
+ tm.tm_hour--;
+ tm.tm_mon--;
+ if (century == FST_FLAG_CENTURY)
+ tm.tm_year += 100;
+
+ time = mktime(&tm);
+ if (time == -1) {
+ fprintf(stderr, COMP "mktime failed!\n");
+ memset(&time, 0, sizeof(time));
+ }
+ return time;
+}
+
+/*
+ * Read one FST entry into *fst from offset on disk addr and detect type.
+ *
+ * Return values:
+ * ret > 0 : disk address of additional FOP block
+ * ret = -1 : file entry filled
+ * ret = -2 : end of directory
+ * ret = -3 : directory entry
+ * ret = -4 : allocmap entry
+ */
+static int readdir_entry(struct fst_entry *fst, off_t addr)
+{
+ int rc;
+
+ BUG(addr & (sizeof(struct fst_entry) - 1));
+
+ rc = _read(fst, sizeof(*fst), addr);
+ BUG(rc < 0);
+
+ if (is_directory(fst->name, fst->type)) {
+ /* check for multi-block directory */
+ if (ABS(fst->fop) != addr)
+ return ABS(fst->fop);
+ return READDIR_DIR_ENTRY;
+ }
+
+ if (is_allocmap(fst->name, fst->type))
+ return READDIR_MAP_ENTRY;
+
+ if (is_file((unsigned long long *) fst->name,
+ (unsigned long long *) fst->type))
+ return READDIR_FILE_ENTRY;
+
+ return READDIR_END_OF_DIR;
+}
+
+/*
+ * Return number of characters excluding trailing spaces.
+ */
+static inline int strip_right(const char *str, int size)
+{
+ while (str[size - 1] == 0x20)
+ size--;
+ return size;
+}
+
+/*
+ * Convert ASCII name to EBCDIC name.
+ */
+static int encode_edf_name(const char *name, char *fname, char *ftype)
+{
+ int dot_pos, tlen;
+ char *tmp;
+
+ /*
+ * name is ascii string "FILE.EXT"
+ * readdir_entry returns fst.name fst.type as EBCDIC including spaces
+ * pre-fill name and type with ascii spaces, remove dot and convert
+ * to EBCDIC.
+ */
+ memset(fname, 0x20, 8);
+ memset(ftype, 0x20, 8);
+
+ tmp = index(name, '.');
+ /* filenames without a dot are invalid! */
+ if (tmp == NULL)
+ return -EINVAL;
+
+ dot_pos = tmp - name;
+ if (dot_pos == 0 || dot_pos > 8)
+ return -EINVAL;
+ memcpy(fname, name, dot_pos);
+ ebcdic_enc(fname, fname, 8);
+
+ tlen = strlen(name) - (dot_pos + 1);
+ if (tlen == 0 || tlen > 8)
+ return -EINVAL;
+
+ memcpy(ftype, name + dot_pos + 1, tlen);
+ ebcdic_enc(ftype, ftype, 8);
+ return 0;
+}
+
+/*
+ * Convert EBCDIC name to ASCII name.
+ */
+static void decode_edf_name(char *file, char *fname, char *ftype)
+{
+ int len, pos = 0;
+
+ ebcdic_dec(fname, fname, 8);
+ ebcdic_dec(ftype, ftype, 8);
+
+ /* strip spaces but only from the end */
+ len = strip_right(fname, 8);
+ memcpy(file, fname, len);
+
+ /* add dot */
+ pos += len;
+ file[pos] = '.';
+ pos++;
+
+ len = strip_right(ftype, 8);
+ memcpy(&file[pos], ftype, len);
+ pos += len;
+
+ /* terminate string */
+ file[pos] ='\0';
+}
+
+static int edf_name_valid(const char *name)
+{
+ int name_len, i;
+ char *dot;
+
+ /* name must contain . */
+ dot = index(name, '.');
+ if (dot == NULL)
+ return -EINVAL;
+
+ name_len = dot - name;
+
+ for (i = 0; i < name_len; i++)
+ if (!is_edf_char(name[i]))
+ return -EINVAL;
+ for (i = name_len + 1; i < (int) strlen(name); i++)
+ if (!is_edf_char(name[i]))
+ return -EINVAL;
+ return 0;
+}
+
+/*
+ * Summarize the number of bytes used in the last data block.
+ */
+static int walk_last_var_data_block(off_t addr, ssize_t *total)
+{
+ ssize_t left = cmsfs.blksize;
+ u16 len;
+ int rc;
+
+ /* subtract displacement */
+ left -= addr & DATA_BLOCK_MASK;
+
+ while (left >= (int) sizeof(len)) {
+
+ rc = _read(&len, sizeof(len), addr);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Null length means no more records follow.
+ * Assumption: the last block is zero-padded.
+ */
+ if (!len)
+ return 0;
+
+ /* add length of record with the header length */
+ *total += len + sizeof(len);
+
+ left -= len + sizeof(len);
+
+ /* point to next record */
+ addr += len + sizeof(len);
+ }
+ return 0;
+}
+
+/*
+ * Return struct record for record number nr.
+ */
+static struct record *get_record(struct file *f, int nr)
+{
+ BUG(nr > f->fst->nr_records - 1);
+ return &f->rlist[nr];
+}
+
+static int skip_header_byte(struct file *f)
+{
+ if (f->fst->record_format == RECORD_LEN_FIXED)
+ return 0;
+
+ if (f->record_scan_state == RSS_HEADER_STARTED)
+ return 1;
+ else
+ return 0;
+}
+
+static void set_record_len_upper(struct file *f, int record, u8 len)
+{
+ struct record *r = &f->rlist[record];
+
+ if (f->record_scan_state != RSS_DATA_BLOCK_STARTED &&
+ f->record_scan_state != RSS_DATA_BLOCK_EXT)
+ DIE("%s: internal error\n", __func__);
+
+ r->total_len = len << 8;
+ f->record_scan_state = RSS_HEADER_STARTED;
+}
+
+static void set_record_len_lower(struct file *f, int record, u8 len)
+{
+ struct record *r = &f->rlist[record];
+
+ if (f->record_scan_state != RSS_HEADER_STARTED)
+ DIE("%s: internal error\n", __func__);
+
+ r->total_len += len;
+ f->record_scan_state = RSS_HEADER_COMPLETE;
+}
+
+static void set_record_len(struct file *f, int record, u16 len)
+{
+ struct record *r = &f->rlist[record];
+
+ if (f->fst->nr_records && f->fst->nr_records == record)
+ DIE("%s: record nr: %d out of bounds\n", __func__, record);
+
+ if (f->record_scan_state != RSS_DATA_BLOCK_STARTED &&
+ f->record_scan_state != RSS_DATA_BLOCK_EXT)
+ DIE("%s: internal error\n", __func__);
+
+ r->total_len = len;
+ f->record_scan_state = RSS_HEADER_COMPLETE;
+}
+
+static void set_record(struct file *f, int *record, off_t addr, int len,
+ size_t *total, int block)
+{
+ struct record *r = &f->rlist[*record];
+
+ if (f->record_scan_state != RSS_HEADER_COMPLETE)
+ DIE("%s: internal error\n", __func__);
+
+ r->first_block_len = len;
+ r->disk_start = addr;
+ r->block_nr = block;
+
+ if (addr == NULL_BLOCK) {
+ if (f->null_ctr % cmsfs.blksize == 0)
+ r->null_block_started = 1;
+ f->null_ctr += len;
+ } else
+ f->null_ctr = 0;
+
+ /* add previous record linefeed but not for the first record */
+ if (f->linefeed && *record)
+ (*total)++;
+ r->file_start = *total;
+ (*total) += r->total_len;
+ f->record_scan_state = RSS_DATA_BLOCK_STARTED;
+}
+
+static void add_record_ext(struct record *rec, struct record_ext *ext)
+{
+ struct record_ext *tmp;
+ int i = 0;
+
+ if (rec->ext == NULL) {
+ rec->ext = ext;
+ ext->prev = NULL;
+ ext->next = NULL;
+ } else {
+ tmp = rec->ext;
+ i++;
+ while (tmp->next != NULL) {
+ i++;
+ tmp = tmp->next;
+ }
+ tmp->next = ext;
+ ext->prev = tmp;
+ ext->next = NULL;
+ }
+}
+
+static void set_record_extension(struct file *f, int *record, off_t addr,
+ int len, int block)
+{
+ struct record *rec = &f->rlist[*record];
+ struct record_ext *ext;
+
+ if (f->record_scan_state != RSS_DATA_BLOCK_STARTED &&
+ f->record_scan_state != RSS_DATA_BLOCK_EXT)
+ DIE("%s: interal error\n", __func__);
+
+ BUG(*record >= f->fst->nr_records);
+
+ ext = malloc(sizeof(struct record_ext));
+ if (ext == NULL)
+ DIE_PERROR("malloc failed\n");
+ memset(ext, 0, sizeof(*ext));
+ ext->len = len - skip_header_byte(f);
+ ext->disk_start = addr + skip_header_byte(f);
+ ext->block_nr = block;
+
+ if (ext->disk_start == NULL_BLOCK) {
+ if (f->null_ctr % cmsfs.blksize == 0)
+ ext->null_block_started = 1;
+ f->null_ctr += len;
+ } else
+ f->null_ctr = 0;
+
+ add_record_ext(rec, ext);
+ f->record_scan_state = RSS_DATA_BLOCK_EXT;
+}
+
+static int end_of_file(struct file *f, int record)
+{
+ if (record == f->fst->nr_records)
+ return 1;
+ return 0;
+}
+
+static void walk_fixed_data_block(struct file *f, off_t addr, int *record,
+ size_t *total, int block, int disp)
+{
+ off_t offset = block * cmsfs.blksize + disp;
+ int rlen = (f->fst->record_len > cmsfs.blksize) ?
+ cmsfs.blksize : f->fst->record_len;
+ int first = (offset % f->fst->record_len) ?
+ rlen - (offset % rlen) : 0;
+ int left = cmsfs.blksize - disp;
+
+ if (first) {
+ BUG(first > left);
+ set_record_extension(f, record, addr, first, block);
+ left -= first;
+ if (addr != NULL_BLOCK)
+ addr += first;
+ }
+
+ while (left >= rlen) {
+ /*
+ * Increment record number only after adding a possible
+ * extension. *record starts with -1 so the first is 0.
+ */
+ (*record)++;
+ if (end_of_file(f, *record))
+ return;
+
+ set_record_len(f, *record, f->fst->record_len);
+ set_record(f, record, addr, rlen, total, block);
+
+ left -= rlen;
+ if (addr != NULL_BLOCK)
+ addr += rlen;
+ }
+
+ /* partial record left */
+ if (left > 0) {
+ (*record)++;
+ if (end_of_file(f, *record))
+ return;
+
+ set_record_len(f, *record, f->fst->record_len);
+ set_record(f, record, addr, left, total, block);
+ return;
+ }
+}
+
+static int get_record_unused_bytes(struct file *f, int nr)
+{
+ struct record *rec = get_record(f, nr);
+ struct record_ext *rext;
+ int used = 0;
+
+ /* no data bytes yet */
+ if (f->record_scan_state == RSS_HEADER_COMPLETE)
+ return rec->total_len;
+
+ used = rec->first_block_len;
+
+ /* only first block */
+ if (f->record_scan_state == RSS_DATA_BLOCK_STARTED)
+ goto out;
+ rext = rec->ext;
+ while (rext != NULL) {
+ used += rext->len;
+ rext = rext->next;
+ }
+out:
+ return rec->total_len - used;
+}
+
+static int walk_var_data_block(struct file *f, off_t addr, unsigned int disp,
+ int *record, size_t *total, int block, int skip)
+{
+ ssize_t left = cmsfs.blksize - skip;
+ int last, rc;
+ u8 half_len;
+ u16 len;
+
+ /*
+ * If records are skipped on this block there is no record extension,
+ * overwrite disp and start with scanning the record.
+ */
+ if (skip)
+ disp = 0;
+
+ /*
+ * disp set means 1 or 2 header bytes and possibly data bytes on the
+ * last block or a null block.
+ */
+ if (disp) {
+ if (addr == NULL_BLOCK) {
+ last = cmsfs.blksize;
+
+ /*
+ * Special case: last block can be a null block with
+ * not all bytes used on it.
+ */
+ if (f->fst->nr_blocks - 1 == block)
+ last = get_record_unused_bytes(f, *record);
+
+ /*
+ * Special case: record header on last block wo. data.
+ * That means no record data yet for this block.
+ */
+ if (f->record_scan_state == RSS_HEADER_COMPLETE)
+ set_record(f, record, addr, last, total, block);
+ else
+ set_record_extension(f, record, addr, last,
+ block);
+ return 0;
+ }
+
+ if (disp == VAR_RECORD_SPANNED)
+ len = cmsfs.blksize;
+ else
+ len = disp;
+
+
+ /* split header -> read second length byte */
+ if (f->record_scan_state == RSS_HEADER_STARTED) {
+ rc = _read(&half_len, sizeof(half_len), addr);
+ if (rc < 0)
+ return rc;
+ set_record_len_lower(f, *record, half_len);
+ left--;
+ len--;
+ addr++;
+ }
+
+ if (f->record_scan_state == RSS_HEADER_COMPLETE)
+ set_record(f, record, addr, len, total, block);
+ else
+ set_record_extension(f, record, addr, len, block);
+
+ if (disp == VAR_RECORD_SPANNED)
+ return 0;
+
+ left -= len;
+ addr += len;
+ }
+
+ /* at least one data byte */
+ while (left >= (int) sizeof(len) + 1) {
+
+ rc = _read(&len, sizeof(len), addr);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Null length means no more records follow.
+ * Assumption: the last block is zero-padded.
+ */
+ if (!len)
+ return 0;
+
+ /*
+ * Increment record number only after adding a possible
+ * extension. *record starts with -1 so the first is 0.
+ */
+ (*record)++;
+ set_record_len(f, *record, len);
+
+ /* account consumed header bytes */
+ left -= sizeof(len);
+ addr += sizeof(len);
+
+ /* limit to block end */
+ if (len > left)
+ len = left;
+
+ /* add length of record including the header length */
+ set_record(f, record, addr, len, total, block);
+
+ left -= len;
+ /* point to next record header */
+ addr += len;
+ }
+
+ /* 2 header bytes left */
+ if (left == 2) {
+ rc = _read(&len, sizeof(len), addr);
+ if (rc < 0)
+ return rc;
+ if (!len)
+ return 0;
+
+ (*record)++;
+ set_record_len(f, *record, len);
+ return 0;
+ }
+
+ /* split header */
+ if (left == 1) {
+ if (end_of_file(f, *record + 1))
+ return 0;
+ rc = _read(&half_len, sizeof(half_len), addr);
+ if (rc < 0)
+ return rc;
+ (*record)++;
+ set_record_len_upper(f, *record, half_len);
+ f->record_scan_state = RSS_HEADER_STARTED;
+ }
+ return 0;
+}
+
+static void cache_fixed_data_block(struct file *f, off_t addr, int *block,
+ int *record, size_t *total, int disp)
+{
+ /*
+ * Cannot distinguish null block pointers from not existing pointers,
+ * so this fn is called for the whole pointer block and maybe for
+ * non-existing blocks and records too. Check and bail out if EOF.
+ */
+ if (*block >= f->fst->nr_blocks)
+ return;
+
+ walk_fixed_data_block(f, addr, record, total, *block, disp);
+ f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
+ f->blist[*block].hi_record_nr = *record + 1;
+ (*block)++;
+}
+
+/*
+ * Walk all pointer blocks of a fixed file and call function for every
+ * data block respecting the sequence of the data.
+ */
+static int cache_file_fixed(struct file *f, off_t addr, int level, int *block,
+ unsigned int *disp, int *record, size_t *total)
+{
+ int left = f->ptr_per_block;
+ off_t ptr;
+
+ if (level > 0) {
+ level--;
+ while (left--) {
+ ptr = get_fixed_pointer(addr);
+ if (ptr < 0)
+ return ptr;
+ cache_file_fixed(f, ptr, level, block, disp, record, total);
+ /* don't increment for null block pointers */
+ if (addr)
+ addr += PTR_SIZE;
+ }
+ return 0;
+ }
+ cache_fixed_data_block(f, addr, block, record, total, 0);
+ return 0;
+}
+
+static int cache_variable_data_block(struct file *f, off_t addr, int *block,
+ int *record, int disp, size_t *total, int skip)
+{
+ int rc;
+
+ /*
+ * Cannot distinguish null block pointers from not existing pointers,
+ * so this fn is called for the whole pointer block and maybe for
+ * non-existing blocks and records too. Check and bail out if EOF.
+ */
+ if (*block >= f->fst->nr_blocks ||
+ *record >= f->fst->nr_records)
+ return 0;
+
+ rc = walk_var_data_block(f, addr, disp, record, total, *block, skip);
+ if (rc < 0)
+ return rc;
+
+ f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
+ /* record starts with 0 but on-disk record number with 1 */
+ f->blist[*block].hi_record_nr = *record + 1;
+ f->blist[*block].disp = disp;
+ (*block)++;
+ return 0;
+}
+
+/*
+ * Walk all pointer blocks of a variable file and call function for every
+ * data block respecting the sequence of the data.
+ */
+static int cache_file_variable(struct file *f, off_t addr, int level,
+ int *block, unsigned int *disp,
+ int *record, size_t *total)
+{
+ int nr, left = f->ptr_per_block;
+ off_t ptr;
+
+ if (level > 0) {
+ level--;
+ /* 4 or 8 bytes are left at the end (offset) which we ignore */
+ while (left--) {
+ ptr = get_var_pointer(addr, &nr, disp);
+ if (ptr < 0)
+ return ptr;
+ if (ptr == VAR_FILE_END)
+ return 0;
+ cache_file_variable(f, ptr, level, block,
+ disp, record, total);
+ addr += VPTR_SIZE;
+ }
+ return 0;
+ }
+ return cache_variable_data_block(f, addr, block, record, *disp, total, 0);
+}
+
+static int locate_last_data_vptr(off_t addr, int level,
+ struct fst_entry *fst, struct var_ptr *vptr)
+{
+ int last, rc;
+
+ if (!level)
+ return 0;
+ level--;
+
+ /* read offset pointer from the end of the var pointer block */
+ rc = _read(&last, sizeof(last), addr + cmsfs.blksize - sizeof(last));
+ if (rc < 0)
+ return rc;
+
+ if (last % VPTR_SIZE || last > cmsfs.blksize)
+ return -EIO;
+ rc = _read(vptr, VPTR_SIZE, addr + last);
+ if (rc < 0)
+ return rc;
+ if (vptr->hi_record_nr != fst->nr_records)
+ return -EIO;
+
+ /* vptr should contain the highest data block pointer */
+ if (!level)
+ return 0;
+
+ if (vptr->next == NULL_BLOCK)
+ return 0;
+
+ return locate_last_data_vptr(ABS(vptr->next), level, fst, vptr);
+}
+
+static int is_textfile(struct fst_entry *fst)
+{
+ char type[MAX_TYPE_LEN];
+ struct filetype *ft;
+
+ if (!fst)
+ return 0;
+
+ memset(type, 0, sizeof(type));
+ ebcdic_dec(type, fst->type, 8);
+
+ list_iterate(ft, &text_type_list, list)
+ if (strncmp(ft->name, type, strlen(ft->name)) == 0)
+ return 1;
+ return 0;
+}
+
+/*
+ * Decide if linefeeds are needed for this file type.
+ */
+static int linefeed_mode_enabled(struct fst_entry *fst)
+{
+ if (cmsfs.mode == BINARY_MODE)
+ return 0;
+ if (cmsfs.mode == TEXT_MODE)
+ return 1;
+ return is_textfile(fst);
+}
+
+/*
+ * Workaround glibc 2.9 bug with less than 3 files and give room for some
+ * new files. If cache is full it will be purged and rebuild.
+ */
+static int max_cache_entries(void)
+{
+ return cmsfs.files + 10 + cmsfs.files / 4;
+}
+
+static void resize_htab(void)
+{
+ int i;
+
+ for (i = 0; i < cmsfs.fcache_used; i++)
+ free(cmsfs.fcache[i].str);
+ hdestroy_r(&cmsfs.htab);
+ free(cmsfs.fcache);
+ cmsfs.fcache_used = 0;
+ cmsfs.fcache_max = max_cache_entries();
+
+ cmsfs.fcache = calloc(cmsfs.fcache_max, sizeof(struct fcache_entry));
+ if (!hcreate_r(cmsfs.fcache_max, &cmsfs.htab))
+ DIE("hcreate failed\n");
+}
+
+static void cache_fst_addr(off_t addr, const char *file)
+{
+ struct fcache_entry *fce;
+ ENTRY e, *eptr;
+
+ e.key = strdup(file);
+
+again:
+ if (hsearch_r(e, FIND, &eptr, &cmsfs.htab) == 0) {
+ /* cache it */
+ if (cmsfs.fcache_used == cmsfs.fcache_max - 1) {
+ DEBUG("hsearch: hash table full: %d\n", cmsfs.fcache_used);
+ resize_htab();
+ goto again;
+ }
+
+ fce = &cmsfs.fcache[cmsfs.fcache_used];
+ cmsfs.fcache_used++;
+ fce->fst_addr = addr;
+ fce->str = e.key;
+
+ e.data = fce;
+ if (hsearch_r(e, ENTER, &eptr, &cmsfs.htab) == 0)
+ DIE("hsearch: hash table full\n");
+ } else
+ free(e.key);
+}
+
+static void update_htab_entry(off_t addr, const char *file)
+{
+ struct fcache_entry *fce;
+ ENTRY e, *eptr;
+
+ e.key = strdup(file);
+
+ if (hsearch_r(e, FIND, &eptr, &cmsfs.htab) == 0) {
+ /* not yet cached, nothing to do */
+ free(e.key);
+ return;
+ } else {
+ /* update it */
+ fce = eptr->data;
+ fce->fst_addr = addr;
+ e.data = fce;
+ if (hsearch_r(e, ENTER, &eptr, &cmsfs.htab) == 0)
+ DIE("%s: hash table full\n", __func__);
+ }
+}
+
+static void invalidate_htab_entry(const char *name)
+{
+ struct fcache_entry *fce;
+ ENTRY e, *eptr;
+
+ e.key = strdup(name);
+
+ if (hsearch_r(e, FIND, &eptr, &cmsfs.htab) == 0) {
+ /* nothing to do if not cached */
+ free(e.key);
+ return;
+ }
+
+ fce = eptr->data;
+ fce->fst_addr = 0;
+ e.data = fce;
+ if (hsearch_r(e, ENTER, &eptr, &cmsfs.htab) == 0)
+ DIE("hsearch: hash table full\n");
+}
+
+/*
+ * For each FST entry in a directory block do action.
+ *
+ * Return:
+ * hit == NULL : lookup file not found
+ * hit != NULL : lookup file found, addr of the fst entry
+ */
+static void walk_dir_block(struct fst_entry *fst, struct walk_file *walk,
+ int level, off_t *hit)
+{
+ off_t ptr, addr = walk->addr;
+ char file[MAX_FNAME];
+ int ret, left;
+
+ /* handle higher level directory pointer blocks */
+ if (level > 0) {
+ level--;
+ left = PTRS_PER_BLOCK;
+ while (left--) {
+ ptr = get_fixed_pointer(addr);
+ BUG(ptr < 0);
+ if (!ptr)
+ break;
+ walk->addr = ptr;
+ walk_dir_block(fst, walk, level, hit);
+ if (hit != NULL && *hit)
+ return;
+ addr += PTR_SIZE;
+ }
+ return;
+ }
+
+ if (walk->flag == WALK_FLAG_CACHE_DBLOCKS) {
+ walk->dlist[walk->dlist_used++] = walk->addr;
+ return;
+ }
+
+ left = cmsfs.blksize / sizeof(struct fst_entry);
+ while (left--) {
+ ret = readdir_entry(fst, walk->addr);
+
+ /* directory and allocmap type are skipped */
+
+ if (ret == READDIR_FILE_ENTRY) {
+ if (walk->flag == WALK_FLAG_LOOKUP) {
+ if ((memcmp(fst->name, walk->name, 8) == 0) &&
+ (memcmp(fst->type, walk->type, 8) == 0)) {
+ /* got it */
+ *hit = walk->addr;
+ return;
+ }
+ }
+
+ if (walk->flag == WALK_FLAG_READDIR) {
+ memset(file, 0, sizeof(file));
+ decode_edf_name(file, fst->name, fst->type);
+ if (!file_unlinked(file)) {
+ cache_fst_addr(walk->addr, file);
+ walk->filler(walk->buf, file, NULL, 0);
+ }
+ }
+ }
+
+ if (ret == READDIR_END_OF_DIR) {
+ if (walk->flag == WALK_FLAG_LOCATE_EMPTY) {
+ *hit = walk->addr;
+ return;
+ }
+ break;
+ }
+ walk->addr += sizeof(struct fst_entry);
+ };
+ return;
+}
+
+static void walk_directory(struct fst_entry *fst, struct walk_file *walk,
+ off_t *hit)
+{
+ if (cmsfs.dir_levels == 0)
+ walk->addr = cmsfs.fdir;
+ else
+ walk->addr = get_fop(cmsfs.fdir);
+ walk_dir_block(fst, walk, cmsfs.dir_levels, hit);
+}
+
+/*
+ * Check FST record format only when reading FST entry from disk.
+ */
+static int check_fst_valid(struct fst_entry *fst)
+{
+ if (fst->record_format != RECORD_LEN_FIXED &&
+ fst->record_format != RECORD_LEN_VARIABLE)
+ return 0;
+ else
+ return 1;
+}
+
+/*
+ * Locate the file's fst_entry in any of the directory blocks.
+ */
+static off_t lookup_file(const char *name, struct fst_entry *fst, int flag)
+{
+ struct fcache_entry *fce;
+ char uc_name[MAX_FNAME];
+ char fname[8], ftype[8];
+ struct walk_file walk;
+ ENTRY e, *eptr;
+ off_t faddr = 0;
+ int rc;
+
+ strncpy(uc_name, name, MAX_FNAME);
+ str_toupper(uc_name);
+
+ if (flag == HIDE_UNLINKED && file_unlinked(uc_name))
+ return 0;
+
+ e.key = strdup(uc_name);
+
+ /* already cached ? */
+ if (hsearch_r(e, FIND, &eptr, &cmsfs.htab)) {
+ fce = eptr->data;
+
+ /* check if fst is valid, may be zero for a stale entry */
+ if (!fce->fst_addr)
+ goto renew;
+
+ /* read in the fst entry */
+ rc = _read(fst, sizeof(*fst), fce->fst_addr);
+ BUG(rc < 0);
+
+ if (!check_fst_valid(fst))
+ DIE("Invalid file format in file: %s\n", uc_name);
+
+ free(e.key);
+ return fce->fst_addr;
+ }
+
+renew:
+ free(e.key);
+ if (encode_edf_name(uc_name, fname, ftype))
+ return 0;
+ memset(&walk, 0, sizeof(walk));
+ walk.flag = WALK_FLAG_LOOKUP;
+ walk.name = fname;
+ walk.type = ftype;
+ walk_directory(fst, &walk, &faddr);
+ if (!faddr)
+ return 0;
+ if (!check_fst_valid(fst))
+ DIE("Invalid file format in file: %s\n", uc_name);
+ cache_fst_addr(faddr, uc_name);
+ return faddr;
+}
+
+static int cache_file(struct file *f)
+{
+ int block = 0, record = -1;
+ unsigned int disp = 0;
+ size_t total = 0;
+
+ return f->fops->cache_data(f, ABS(f->fst->fop), f->fst->levels,
+ &block, &disp, &record, &total);
+}
+
+/*
+ * Caveat: for fixed files nr_blocks is excluding null blocks,
+ * for variable files nr_blocks is including null blocks.
+ * Add null blocks for fixed files so allocation and file end
+ * checks work identical for both variants.
+ */
+static void workaround_nr_blocks(struct file *f)
+{
+ int nr;
+
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ return;
+ nr = f->fst->nr_records * f->fst->record_len / cmsfs.blksize;
+ if (f->fst->nr_records * f->fst->record_len % cmsfs.blksize)
+ nr++;
+ f->nr_null_blocks = nr - f->fst->nr_blocks;
+ f->fst->nr_blocks = nr;
+}
+
+static ssize_t get_file_size_fixed(struct fst_entry *fst)
+{
+ return fst->nr_records * fst->record_len;
+}
+
+static ssize_t get_file_size_variable_slow(struct fst_entry *fst)
+{
+ struct record *rec;
+ ssize_t total = 0;
+ int rc = 0;
+ struct file *f = create_file_object(fst, &rc);
+
+ if (f == NULL)
+ return rc;
+
+ rec = get_record(f, f->fst->nr_records - 1);
+ total = rec->file_start + rec->total_len;
+
+ /*
+ * Note: need to add header bytes since the record information does
+ * not contain them but get_file_size_logical will remove them...
+ */
+ total += f->fst->nr_records * VAR_RECORD_HEADER_SIZE;
+ destroy_file_object(f);
+ return total;
+}
+
+static ssize_t get_file_size_variable(struct fst_entry *fst)
+{
+ struct var_ptr vptr;
+ ssize_t total = 0;
+ off_t ptr;
+ int rc;
+
+ if (fst->levels > 0) {
+ rc = locate_last_data_vptr(ABS(fst->fop), fst->levels, fst,
+ &vptr);
+ if (rc < 0)
+ return rc;
+ if (vptr.next == 0) {
+ /*
+ * Last block is a null block. Cannot scan that block,
+ * need to scan the whole file instead...
+ */
+ total = get_file_size_variable_slow(fst);
+ goto skip;
+ }
+ ptr = ABS(vptr.next);
+ if (vptr.disp != VAR_RECORD_SPANNED) {
+ ptr += vptr.disp;
+ /* count displacement as used space */
+ total += vptr.disp;
+ } else {
+ total += cmsfs.blksize;
+ goto skip_scan;
+ }
+ } else
+ ptr = ABS(fst->fop);
+
+ /* now count the remaining used space in the last block */
+ rc = walk_last_var_data_block(ptr, &total);
+ if (rc < 0)
+ return rc;
+
+skip_scan:
+ /*
+ * Add the full blocks. For variable record file nr_blocks contains
+ * also null blocks.
+ */
+ if (fst->nr_blocks)
+ total += (fst->nr_blocks - 1) * cmsfs.blksize;
+skip:
+ return total;
+}
+
+/*
+ * Return the file size as it is on the disk. Includes headers for
+ * variable records.
+ */
+static ssize_t get_file_size(struct fst_entry *fst)
+{
+ if (fst->record_format == RECORD_LEN_FIXED)
+ return get_file_size_fixed(fst);
+ else if (fst->record_format == RECORD_LEN_VARIABLE)
+ return get_file_size_variable(fst);
+ return 0;
+}
+
+static ssize_t get_file_size_logical(struct fst_entry *fst)
+{
+ ssize_t total;
+
+ if (fst->nr_records == 0)
+ return 0;
+ if (!fst->fop)
+ return -EIO;
+ total = get_file_size(fst);
+ if (total < 0)
+ return -EIO;
+
+ /* subtract the record headers */
+ if (fst->record_format == RECORD_LEN_VARIABLE)
+ total -= fst->nr_records * VAR_RECORD_HEADER_SIZE;
+
+ if (linefeed_mode_enabled(fst))
+ total += fst->nr_records;
+ return total;
+}
+
+static int cmsfs_getattr(const char *path, struct stat *stbuf)
+{
+ int mask = (cmsfs.allow_other) ? 0444 : 0440;
+ struct fst_entry fst;
+
+ if (!cmsfs.readonly)
+ mask |= ((cmsfs.allow_other) ? 0222 : 0220);
+
+ memset(stbuf, 0, sizeof(*stbuf));
+ stbuf->st_uid = getuid();
+ stbuf->st_gid = getgid();
+ stbuf->st_blksize = cmsfs.blksize;
+
+ if (strcmp(path, "/") == 0) {
+ stbuf->st_mode = S_IFDIR | mask |
+ ((cmsfs.allow_other) ? 0111 : 0110);
+ stbuf->st_nlink = 2;
+
+ readdir_entry(&fst, cmsfs.fdir);
+
+ /* date */
+ stbuf->st_mtime = fst_date_to_time_t(&fst.date[0],
+ fst.flag & FST_FLAG_CENTURY);
+ stbuf->st_atime = stbuf->st_ctime = stbuf->st_mtime;
+
+ /* size */
+ stbuf->st_size = fst.record_len * fst.nr_records;
+ stbuf->st_blocks = max(stbuf->st_size, cmsfs.blksize);
+ stbuf->st_blocks = ((stbuf->st_blocks + cmsfs.data_block_mask) &
+ ~cmsfs.data_block_mask) >> 9;
+ } else {
+ if (!lookup_file(path + 1, &fst, HIDE_UNLINKED))
+ return -ENOENT;
+
+ stbuf->st_mode = S_IFREG | mask;
+ stbuf->st_nlink = 1;
+
+ /* date */
+ stbuf->st_mtime = stbuf->st_atime = stbuf->st_ctime =
+ fst_date_to_time_t(&fst.date[0],
+ fst.flag & FST_FLAG_CENTURY);
+ /* size */
+ stbuf->st_size = get_file_size_logical(&fst);
+ if (stbuf->st_size < 0)
+ return -EIO;
+ /*
+ * Include potential sparse blocks for variable files which
+ * are included in nr_blocks to avoid scanning the whole file.
+ */
+ stbuf->st_blocks = fst.nr_blocks * cmsfs.nr_blocks_512;
+ }
+ return 0;
+}
+
+static int cmsfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi)
+{
+ struct walk_file walk;
+ struct fst_entry fst;
+
+ (void) offset;
+ (void) fi;
+
+ /*
+ * Offset is ignored and 0 passed to the filler fn so the whole
+ * directory is read at once.
+ */
+
+ /* EDF knows only the root directory */
+ if (strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ filler(buf, ".", NULL, 0);
+ filler(buf, "..", NULL, 0);
+
+ memset(&walk, 0, sizeof(walk));
+ /* readdir is possible without open so fi->fh is not set */
+ walk.flag = WALK_FLAG_READDIR;
+ walk.buf = buf;
+ walk.filler = filler;
+ walk_directory(&fst, &walk, NULL);
+ return 0;
+}
+
+static int cmsfs_open(const char *path, struct fuse_file_info *fi)
+{
+ struct fst_entry fst;
+ struct file *f;
+ off_t fst_addr;
+ int rc = 0;
+
+ /*
+ * open flags:
+ * O_DIRECTORY: FUSE captures open on / so not needed.
+ * O_NOATIME: ignored because there is no atime in EDF.
+ * O_NOFOLLOW: can be ignored since EDF has no links.
+ * O_SYNC: ignored since IO is alwasy sync.
+ * O_TRUNC, O_CREAT, O_EXCL: avoided by FUSE.
+ */
+ fst_addr = lookup_file(path + 1, &fst, SHOW_UNLINKED);
+ if (!fst_addr)
+ return -ENOENT;
+
+ f = file_open(path + 1);
+ if (f == NULL) {
+ f = create_file_object(&fst, &rc);
+ if (f == NULL)
+ return rc;
+ f->fst_addr = fst_addr;
+
+ /*
+ * Store file size in file object. Needed for write of fixed record
+ * length files when the write is not a multiple of the record length.
+ * In this case a second write would fail since the file size would
+ * be calculated by lrecl * nr_records. Use session_size therefore.
+ */
+ f->session_size = get_file_size_logical(&fst);
+ if (f->session_size < 0)
+ return -EIO;
+
+ f->wcache = malloc(WCACHE_MAX);
+ if (f->wcache == NULL)
+ return -ENOMEM;
+
+ strncpy(f->path, path, MAX_FNAME + 1);
+ str_toupper(f->path);
+
+ f->use_count = 1;
+ list_add(&f->list, &open_file_list);
+ } else
+ f->use_count++;
+
+ if (fi->flags & O_RDWR || fi->flags & O_WRONLY)
+ f->write_count++;
+
+ fi->fh = (u64) f;
+ return 0;
+}
+
+static void set_fdir_date_current(void)
+{
+ struct fst_entry fst;
+ int rc;
+
+ rc = _read(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+ set_fst_date_current(&fst);
+ rc = _write(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+}
+
+static void increase_file_count(void)
+{
+ struct fst_entry fst;
+ int rc;
+
+ rc = _read(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+ fst.nr_records = ++cmsfs.files + 2;
+ set_fst_date_current(&fst);
+ rc = _write(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+}
+
+static void decrease_file_count(void)
+{
+ struct fst_entry fst;
+ int rc;
+
+ rc = _read(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+ fst.nr_records = --cmsfs.files + 2;
+ set_fst_date_current(&fst);
+ rc = _write(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+}
+
+static off_t get_reserved_block(void)
+{
+ off_t addr;
+
+ if (cmsfs.reserved_blocks > 0)
+ cmsfs.reserved_blocks--;
+ addr = get_zero_block();
+ BUG(addr < 0);
+ return addr;
+}
+
+static void cache_dblocks(struct walk_file *walk)
+{
+ double dblocks;
+
+ /* calculate number of data blocks used for FST entries */
+ dblocks = (cmsfs.files + 2) * sizeof(struct fst_entry);
+ dblocks = ceil(dblocks / cmsfs.blksize);
+ /* add a spare one in case of create file */
+ dblocks++;
+
+ memset(walk, 0, sizeof(*walk));
+ walk->flag = WALK_FLAG_CACHE_DBLOCKS;
+ walk->dlist = calloc(dblocks, sizeof(off_t));
+ if (walk->dlist == NULL)
+ DIE_PERROR("malloc failed");
+ walk_directory(NULL, walk, NULL);
+}
+
+static void free_dblocks(struct walk_file *walk)
+{
+ free(walk->dlist);
+}
+
+static void purge_dblock_ptrs(int level, off_t addr)
+{
+ int left = PTRS_PER_BLOCK;
+ off_t ptr, start = addr;
+
+ if (!level)
+ return;
+ level--;
+ while (left--) {
+ ptr = get_fixed_pointer(addr);
+ BUG(ptr < 0);
+ if (ptr != NULL_BLOCK)
+ purge_dblock_ptrs(level, ptr);
+
+ /* don't increment for null block pointers */
+ if (addr)
+ addr += PTR_SIZE;
+ }
+ free_block(start);
+}
+
+/*
+ * Return total number of pointer entries for level.
+ */
+static int pointers_per_level(struct file *f, int level, int nr_blocks)
+{
+ double entries = nr_blocks;
+
+ if (!level || nr_blocks < 2)
+ return 0;
+
+ if (level == 1)
+ return nr_blocks;
+
+ level--;
+ while (level--)
+ entries = ceil(entries / f->ptr_per_block);
+ return (int) entries;
+}
+
+static int per_level_fixed(int level, int entries)
+{
+ double val = entries;
+
+ while (level--)
+ val = ceil(val / PTRS_PER_BLOCK);
+ return (int) val;
+}
+
+static void rewrite_dir_ptr_block(struct walk_file *walk,
+ int level, off_t dst, int start)
+{
+ struct fixed_ptr ptr;
+ int rc, i, end;
+ off_t addr;
+
+ if (!level)
+ return;
+
+ end = min(start + PTRS_PER_BLOCK,
+ per_level_fixed(level - 1, walk->dlist_used));
+ BUG(start > end);
+
+ for (i = start; i < end; i++) {
+ if (level == 1) {
+ addr = walk->dlist[i];
+ if (addr)
+ ptr.next = REL(addr);
+ else
+ ptr.next = 0;
+ } else {
+ addr = get_zero_block();
+ BUG(addr < 0);
+ ptr.next = REL(addr);
+ }
+
+ rc = _write(&ptr, sizeof(ptr), dst);
+ BUG(rc < 0);
+ dst += sizeof(ptr);
+
+ rewrite_dir_ptr_block(walk, level - 1, addr,
+ i * PTRS_PER_BLOCK);
+ }
+}
+
+static int update_dir_levels(int blocks)
+{
+ int levels = 1;
+
+ if (blocks < 2)
+ return 0;
+
+ while (blocks / (PTRS_PER_BLOCK + 1)) {
+ levels++;
+ blocks /= PTRS_PER_BLOCK;
+ }
+ return levels;
+}
+
+static void rewrite_dblock_ptrs(struct walk_file *walk)
+{
+ int rc, nr_blocks = walk->dlist_used;
+ struct fst_entry fst;
+ off_t dst;
+
+ BUG(!nr_blocks);
+
+ /* read in the directory FST */
+ rc = _read(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+
+ if (nr_blocks == 1) {
+ fst.fop = REL(cmsfs.fdir);
+ fst.levels = 0;
+ cmsfs.dir_levels = fst.levels;
+ goto store;
+ }
+
+ dst = get_zero_block();
+ BUG(dst < 0);
+ fst.fop = REL(dst);
+
+ fst.levels = update_dir_levels(walk->dlist_used);
+ cmsfs.dir_levels = fst.levels;
+ rewrite_dir_ptr_block(walk, fst.levels, dst, 0);
+store:
+ rc = _write(&fst, sizeof(fst), cmsfs.fdir);
+ BUG(rc < 0);
+}
+
+/*
+ * Update used block count in disk label.
+ */
+static void update_block_count(void)
+{
+ int rc;
+
+ rc = _write(&cmsfs.used_blocks, sizeof(unsigned int), USED_BLOCK_ADDR);
+ BUG(rc < 0);
+}
+
+static int cmsfs_create(const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ char fname[8], ftype[8];
+ char uc_name[MAX_FNAME];
+ struct walk_file walk;
+ struct fst_entry fst;
+ off_t fst_addr = 0;
+ int rc;
+
+ /* no permissions in EDF */
+ (void) mode;
+
+ /*
+ * Note: creating a file that was unlinked but not yet deleted from
+ * disk is not supported. That means the unlinked file can still be
+ * opened.
+ */
+ if (lookup_file(path + 1, &fst, SHOW_UNLINKED))
+ return cmsfs_open(path, fi);
+
+ if (cmsfs.readonly)
+ return -EACCES;
+
+ rc = edf_name_valid(path + 1);
+ if (rc)
+ return rc;
+
+ /* force uppercase */
+ strncpy(uc_name, path + 1, MAX_FNAME);
+ str_toupper(uc_name);
+
+ rc = encode_edf_name(uc_name, fname, ftype);
+ if (rc)
+ return rc;
+
+ /* find free fst entry */
+ memset(&walk, 0, sizeof(walk));
+ walk.flag = WALK_FLAG_LOCATE_EMPTY;
+ walk_directory(&fst, &walk, &fst_addr);
+
+ /* no free slot found, allocate new directory block */
+ if (!fst_addr) {
+ /*
+ * Be conservative and check if enough blocks for all
+ * directory levels are available.
+ */
+ if (cmsfs.total_blocks - cmsfs.used_blocks < cmsfs.dir_levels + 2)
+ return -ENOSPC;
+
+ fst_addr = get_zero_block();
+ if (fst_addr < 0)
+ return fst_addr;
+
+ cache_dblocks(&walk);
+ /* add the newly allocated block to dlist */
+ walk.dlist[walk.dlist_used++] = fst_addr;
+
+ purge_dblock_ptrs(cmsfs.dir_levels, get_fop(cmsfs.fdir));
+ rewrite_dblock_ptrs(&walk);
+ free_dblocks(&walk);
+ update_block_count();
+ }
+
+ /*
+ * Fill fst entry. Default template:
+ * format: variable
+ * record_len: 0
+ * mode: A1
+ * flag: 0, century bit (0x8) set by following utimens
+ * fop: 0
+ * nr_records: 0
+ * nr_blocks: 0
+ * levels: 0
+ * ptr_size: 0xc for variable format
+ * date: set to current date
+ */
+ memset(&fst, 0, sizeof(fst));
+ memcpy(fst.name, fname, 8);
+ memcpy(fst.type, ftype, 8);
+ ebcdic_enc((char *) &fst.mode, "A1", 2);
+ fst.record_format = RECORD_LEN_VARIABLE;
+ fst.ptr_size = sizeof(struct var_ptr);
+
+ rc = set_fst_date_current(&fst);
+ if (rc != 0)
+ return rc;
+
+ rc = _write(&fst, sizeof(fst), fst_addr);
+ BUG(rc < 0);
+ cache_fst_addr(fst_addr, uc_name);
+ increase_file_count();
+ return cmsfs_open(path, fi);
+}
+
+static int purge_pointer_block_fixed(struct file *f, int level, off_t addr)
+{
+ int left = f->ptr_per_block;
+ off_t ptr, start = addr;
+
+ if (!level)
+ return 0;
+
+ level--;
+ while (left--) {
+ if (!level)
+ break;
+
+ ptr = get_fixed_pointer(addr);
+ if (ptr < 0)
+ return ptr;
+ if (ptr != NULL_BLOCK)
+ purge_pointer_block_fixed(f, level, ptr);
+
+ /* don't increment for null block pointers */
+ if (addr)
+ addr += PTR_SIZE;
+ }
+ free_block(start);
+ return 0;
+}
+
+static int purge_pointer_block_variable(struct file *f, int level,
+ off_t addr)
+{
+ int nr, left = f->ptr_per_block;
+ off_t ptr, start = addr;
+ unsigned int disp;
+
+ if (!level)
+ return 0;
+
+ level--;
+ while (left--) {
+ if (!level)
+ break;
+
+ ptr = get_var_pointer(addr, &nr, &disp);
+ if (ptr < 0)
+ return ptr;
+ if (ptr == VAR_FILE_END)
+ break;
+ if (ptr != NULL_BLOCK)
+ purge_pointer_block_variable(f, level, ptr);
+
+ /* don't increment for null block pointers */
+ if (addr)
+ addr += VPTR_SIZE;
+ }
+ free_block(start);
+ return 0;
+}
+
+/*
+ * Store the back pointer for a variable pointer block.
+ * Pointer is offset of last VPTR to block start.
+ */
+static int store_back_pointer(off_t dst, int entries)
+{
+ unsigned int back;
+
+ back = (entries - 1) * VPTR_SIZE;
+ return _write(&back, sizeof(back),
+ ((dst | DATA_BLOCK_MASK) + 1) - sizeof(back));
+}
+
+/*
+ * Rewrite one pointer block starting from the highest level.
+ */
+static int rewrite_pointer_block_fixed(struct file *f, int level, off_t dst,
+ int start)
+{
+ struct fixed_ptr ptr;
+ int i, end, rc = 0;
+ off_t addr;
+
+ if (!level)
+ return 0;
+
+ /*
+ * start is always the first entry of a pointer block,
+ * end is the last used entry in this pointer block.
+ */
+ end = min(start + f->ptr_per_block,
+ per_level_fixed(level - 1, f->fst->nr_blocks));
+ BUG(start > end);
+
+ for (i = start; i < end; i++) {
+ if (level == 1) {
+ addr = f->blist[i].disk_addr;
+ if (addr)
+ ptr.next = REL(addr);
+ else
+ ptr.next = 0;
+ } else {
+ addr = get_reserved_block();
+ ptr.next = REL(addr);
+ }
+
+ rc = _write(&ptr, sizeof(ptr), dst);
+ if (rc < 0)
+ return rc;
+ dst += sizeof(ptr);
+
+ rc = rewrite_pointer_block_fixed(f, level - 1, addr,
+ i * f->ptr_per_block);
+ if (rc < 0)
+ return rc;
+ }
+ return rc;
+}
+
+static int get_first_block_nr(int level, int entry)
+{
+ while (level-- > 1)
+ entry *= VPTRS_PER_BLOCK;
+ return entry;
+}
+
+static int get_last_block_nr(int level, int entry, int nr_blocks)
+{
+ while (level-- > 1)
+ entry *= VPTRS_PER_BLOCK;
+ entry--;
+ if (entry > nr_blocks - 1)
+ entry = nr_blocks - 1;
+ return entry;
+}
+
+static int per_level_var(int level, int entries)
+{
+ double val = entries;
+
+ while (level--)
+ val = ceil(val / VPTRS_PER_BLOCK);
+ return (int) val;
+}
+
+/*
+ * Rewrite one pointer block starting from the highest level.
+ */
+static int rewrite_pointer_block_variable(struct file *f, int level,
+ off_t dst, int start)
+{
+ int i, bnr, end, rc = 0;
+ struct var_ptr ptr;
+ off_t addr;
+
+ if (!level)
+ return 0;
+
+ /*
+ * start is always the first entry of a pointer block,
+ * end is the last used entry in this pointer block.
+ */
+ end = min(start + f->ptr_per_block,
+ per_level_var(level - 1, f->fst->nr_blocks));
+ BUG(start > end);
+
+ for (i = start; i < end; i++) {
+ if (level == 1) {
+ addr = f->blist[i].disk_addr;
+ if (addr)
+ ptr.next = REL(addr);
+ else
+ ptr.next = 0;
+ } else {
+ addr = get_reserved_block();
+ ptr.next = REL(addr);
+ }
+
+ bnr = get_first_block_nr(level, i);
+ ptr.disp = f->blist[bnr].disp;
+
+ bnr = get_last_block_nr(level, i + 1, f->fst->nr_blocks);
+ ptr.hi_record_nr = f->blist[bnr].hi_record_nr;
+
+ rc = _write(&ptr, sizeof(ptr), dst);
+ if (rc < 0)
+ return rc;
+ dst += sizeof(ptr);
+
+ rc = rewrite_pointer_block_variable(f, level - 1, addr,
+ i * f->ptr_per_block);
+ if (rc < 0)
+ return rc;
+ }
+ return store_back_pointer(dst, end - start);
+}
+
+/*
+ * Update fop and pointer blocks if needed.
+ */
+static int rewrite_pointers(struct file *f)
+{
+ struct record *rec;
+ off_t dst;
+
+ if (f->fst->nr_blocks == 0) {
+ f->fst->fop = 0;
+ return 0;
+ }
+
+ if (f->fst->nr_blocks == 1) {
+ rec = get_record(f, 0);
+ if (rec->disk_start == NULL_BLOCK)
+ f->fst->fop = 0;
+ else
+ f->fst->fop = REL(rec->disk_start);
+ return 0;
+ }
+
+ /* allocate root block for fst */
+ dst = get_reserved_block();
+ f->fst->fop = REL(dst);
+ return f->fops->write_pointers(f, f->fst->levels, dst, 0);
+}
+
+/*
+ * Guess position in record table.
+ */
+static int guess_record_number(struct file *f, off_t offset)
+{
+ int nr;
+
+ if (f->linefeed)
+ nr = (offset / (f->fst->record_len + 1));
+ else
+ nr = (offset / f->fst->record_len);
+ if (nr >= f->fst->nr_records)
+ nr = f->fst->nr_records - 1;
+ return nr;
+}
+
+static int offset_is_linefeed(off_t offset, struct record *prev,
+ struct record *next)
+{
+ if ((offset < next->file_start) &&
+ (offset >= prev->file_start + prev->total_len))
+ return 1;
+ return 0;
+}
+
+static int offset_in_record(off_t offset, struct record *rec)
+{
+ if (offset >= rec->file_start &&
+ offset < rec->file_start + rec->total_len)
+ return 1;
+ return 0;
+}
+
+static void set_hint(struct file *f, int hint)
+{
+ f->next_record_hint = hint;
+
+ /* limit hint to last record */
+ if (f->next_record_hint >= f->fst->nr_records)
+ f->next_record_hint = f->fst->nr_records - 1;
+}
+
+/*
+ * Find record by file offset.
+ *
+ * Returns: record number in *nr
+ * > 0 : ptr to found record
+ * -1 : linefeed offset
+ * NULL : error
+ */
+static struct record *find_record(struct file *f, off_t offset, int *nr)
+{
+ int i, start, step, max = f->fst->nr_records;
+ struct record *rec;
+
+ /*
+ * next_record_hint is a guess which is optimal for sequential
+ * single-threaded reads.
+ */
+ i = f->next_record_hint;
+ rec = &f->rlist[i];
+
+ if (offset_in_record(offset, rec)) {
+ /* increment hint for sequential read, fails for extensions */
+ set_hint(f, i + 1);
+ *nr = i;
+ return rec;
+ }
+
+ /* look out for previous record linefeed from sequential read hint */
+ if (f->linefeed && i > 0)
+ if (offset_is_linefeed(offset, &f->rlist[i - 1], rec))
+ return LINEFEED_OFFSET;
+
+ start = guess_record_number(f, offset);
+
+ /* because of potential linefeed we need to check the next record */
+ rec = &f->rlist[start];
+ if (offset < rec->file_start)
+ step = -1;
+ else
+ step = 1;
+
+ for (i = start; i >= 0 && i < max; i += step) {
+ rec = &f->rlist[i];
+ if (offset_in_record(offset, rec)) {
+ set_hint(f, i + 1);
+ *nr = i;
+ return rec;
+ }
+
+ /* last record reached, only one linefeed can follow */
+ if (i == max - 1) {
+ if (f->linefeed &&
+ (offset == rec->file_start + rec->total_len))
+ return LINEFEED_OFFSET;
+ else
+ return NULL;
+ }
+
+ /* check for linefeed byte between two records */
+ if (step == 1) {
+ if (offset_is_linefeed(offset, rec, &f->rlist[i + 1]))
+ return LINEFEED_OFFSET;
+ } else
+ /*
+ * No need to check if i > 0 since no linefeed before
+ * record 0 possible.
+ */
+ if (offset_is_linefeed(offset, &f->rlist[i - 1], rec))
+ return LINEFEED_OFFSET;
+
+ }
+ DEBUG("find: record not found!\n");
+ return NULL;
+}
+
+/*
+ * Get disk address and block size from a record.
+ */
+static void get_block_data_from_record(struct record *rec, off_t offset,
+ off_t *addr, int *chunk)
+{
+ int record_off = offset - rec->file_start;
+ struct record_ext *rext;
+
+ if (record_off >= rec->first_block_len) {
+ record_off -= rec->first_block_len;
+ rext = rec->ext;
+
+ BUG(rext == NULL);
+
+ while (record_off >= rext->len) {
+ record_off -= rext->len;
+ rext = rext->next;
+ BUG(rext == NULL);
+ }
+
+ /* found the right record extension */
+ if (rext->disk_start == NULL_BLOCK)
+ *addr = NULL_BLOCK;
+ else
+ *addr = rext->disk_start + record_off;
+ *chunk = rext->len - record_off;
+ } else {
+ if (rec->disk_start == NULL_BLOCK)
+ *addr = NULL_BLOCK;
+ else
+ *addr = rec->disk_start + record_off;
+ *chunk = rec->first_block_len - record_off;
+ }
+}
+
+static int convert_text(iconv_t conv, char *buf, int size)
+{
+ size_t out_count = size;
+ size_t in_count = size;
+ char *data_ptr = buf;
+ int rc;
+
+ rc = iconv(conv, &data_ptr, &in_count, &data_ptr, &out_count);
+ if ((rc == -1) || (in_count != 0)) {
+ DEBUG("Code page translation EBCDIC-ASCII failed\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int cmsfs_read(const char *path, char *buf, size_t size, off_t offset,
+ struct fuse_file_info *fi)
+{
+ struct file *f = get_fobj(fi);
+ size_t len, copied = 0;
+ struct record *rec;
+ int chunk, nr, rc;
+ off_t addr;
+
+ (void) path;
+
+ len = f->session_size;
+ if ((size_t) offset >= len)
+ return 0;
+
+ if (offset + size > len)
+ size = len - offset;
+
+ while (size > 0) {
+ rec = find_record(f, offset, &nr);
+ if (rec == NULL) {
+ copied = -EINVAL;
+ DEBUG("%s: invalid addr, size: %lu copied: %lu len: %lu\n",
+ __func__, size, copied, len);
+ goto out;
+ }
+
+ /* write linefeed directly to buffer and go to next record */
+ if (rec == LINEFEED_OFFSET) {
+ BUG(!f->linefeed);
+ if (f->translate)
+ *buf = LINEFEED_ASCII;
+ else
+ *buf = LINEFEED_EBCDIC;
+ buf++;
+ copied++;
+ offset++;
+ size--;
+ continue;
+ }
+
+ /* get addr and block size from record */
+ get_block_data_from_record(rec, offset, &addr, &chunk);
+ if (chunk <= 0 || addr < 0)
+ DIE("Invalid record data\n");
+
+ /* copy less if there is not enough space in the fuse buffer */
+ if (size < (size_t) chunk)
+ chunk = size;
+
+ /* read one record */
+ if (addr == NULL_BLOCK)
+ memset(buf, 0, chunk);
+ else {
+ rc = _read(buf, chunk, addr);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (f->translate) {
+ rc = convert_text(cmsfs.iconv_from, buf, chunk);
+ if (rc < 0)
+ return rc;
+ }
+
+ copied += chunk;
+ size -= chunk;
+ buf += chunk;
+ offset += chunk;
+ }
+out:
+ DEBUG("%s: copied: %lu\n", __func__, copied);
+ return copied;
+}
+
+static int cmsfs_statfs(const char *path, struct statvfs *buf)
+{
+ unsigned int inode_size = cmsfs.blksize + sizeof(struct fst_entry);
+ unsigned int free_blocks = cmsfs.total_blocks - cmsfs.used_blocks;
+
+ (void) path;
+
+ buf->f_bsize = buf->f_frsize = cmsfs.blksize;
+ buf->f_blocks = cmsfs.total_blocks;
+ buf->f_bfree = buf->f_bavail = free_blocks;
+ /* number of possible inodes */
+ buf->f_files = cmsfs.total_blocks * cmsfs.blksize / inode_size;
+
+ buf->f_ffree = free_blocks * cmsfs.blksize / inode_size;
+ buf->f_namemax = MAX_FNAME - 1;
+ return 0;
+}
+
+static int cmsfs_utimens(const char *path, const struct timespec ts[2])
+{
+ struct fst_entry fst;
+ off_t fst_addr;
+ struct tm tm;
+ int rc;
+
+ if (cmsfs.readonly)
+ return -EACCES;
+
+ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED);
+ if (!fst_addr)
+ return -ENOENT;
+
+ /* convert timespec to tm */
+ memset(&tm, 0, sizeof(struct tm));
+ if (localtime_r(&ts[0].tv_sec, &tm) == NULL)
+ return -EINVAL;
+
+ update_fst_date(&fst, &tm);
+ rc = _write(&fst, sizeof(fst), fst_addr);
+ BUG(rc < 0);
+ return 0;
+}
+
+static int cmsfs_rename(const char *path, const char *new_path)
+{
+ char uc_old_name[MAX_FNAME];
+ char fname[8], ftype[8];
+ struct fst_entry fst;
+ char *uc_new_name;
+ struct file *f;
+ off_t fst_addr;
+ int rc;
+
+ if (cmsfs.readonly)
+ return -EACCES;
+
+ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED);
+ if (!fst_addr)
+ return -ENOENT;
+
+ rc = edf_name_valid(new_path + 1);
+ if (rc)
+ return rc;
+
+ /* force uppercase */
+ uc_new_name = strdup(new_path + 1);
+ if (uc_new_name == NULL)
+ return -ENOMEM;
+ str_toupper(uc_new_name);
+
+ rc = encode_edf_name(uc_new_name, fname, ftype);
+ if (rc)
+ return rc;
+
+ memcpy(&fst.name[0], fname, 8);
+ memcpy(&fst.type[0], ftype, 8);
+
+ strncpy(uc_old_name, path + 1, MAX_FNAME);
+ str_toupper(uc_old_name);
+ invalidate_htab_entry(uc_old_name);
+
+ /* update name in file object if the file is opened */
+ f = file_open(uc_old_name);
+ if (f != NULL) {
+ strncpy(f->path, new_path, MAX_FNAME + 1);
+ str_toupper(f->path);
+ memcpy(f->fst->name, fname, 8);
+ memcpy(f->fst->type, ftype, 8);
+ }
+
+ rc = _write(&fst, sizeof(fst), fst_addr);
+ BUG(rc < 0);
+ return 0;
+}
+
+static int cmsfs_fsync(const char *path, int datasync,
+ struct fuse_file_info *fi)
+{
+ (void) path;
+ (void) datasync;
+ (void) fi;
+
+ if (cmsfs.readonly)
+ return -EROFS;
+ return msync(cmsfs.map, cmsfs.size, MS_SYNC);
+}
+
+/*
+ * Detect whether the whole block can be freed.
+ */
+static int block_started(struct file *f, struct record *rec)
+{
+ if (rec->disk_start == NULL_BLOCK) {
+ if (rec->null_block_started)
+ return 1;
+ else
+ return 0;
+ }
+
+ if (f->fst->record_format == RECORD_LEN_FIXED) {
+ if (rec->disk_start % cmsfs.blksize == 0)
+ return 1;
+ else
+ return 0;
+ }
+
+ if (f->fst->record_format == RECORD_LEN_VARIABLE) {
+ if ((rec->disk_start % cmsfs.blksize == 0) ||
+ (rec->disk_start % cmsfs.blksize == 1) ||
+ (rec->disk_start % cmsfs.blksize == 2))
+ return 1;
+ else
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Note: only called for the very last record of a file. That means if the
+ * data starts on a block offset the block can be freed. It does not free
+ * header bytes for variable record files if they are on the previous block!
+ */
+static void free_record(struct file *f, struct record *rec)
+{
+ struct record_ext *rext = rec->ext;
+
+ f->fst->nr_records--;
+
+ if (block_started(f, rec)) {
+ free_block(rec->disk_start);
+ f->fst->nr_blocks--;
+ if (!rec->disk_start && f->fst->record_format == RECORD_LEN_FIXED)
+ f->nr_null_blocks--;
+ }
+
+ while (rext != NULL) {
+ /* extensions always start on a new block */
+ free_block(rext->disk_start);
+ f->fst->nr_blocks--;
+ if (!rext->disk_start && f->fst->record_format == RECORD_LEN_FIXED)
+ f->nr_null_blocks--;
+ rext = rext->next;
+ }
+}
+
+static int update_var_header_len(struct file *f, u16 *header,
+ struct record *rec)
+{
+ off_t prev_block_end;
+ int rc, split = 0;
+
+ if (rec->disk_start % cmsfs.blksize == 0)
+ split = 2;
+ if (rec->disk_start % cmsfs.blksize == 1)
+ split = 1;
+
+ /* header is completely in this block */
+ if (!split) {
+ rc = _write(header, sizeof(*header),
+ rec->disk_start - sizeof(*header));
+ if (rc < 0)
+ return rc;
+ return 0;
+ }
+
+ BUG(!rec->block_nr);
+ prev_block_end = f->blist[rec->block_nr - 1].disk_addr | DATA_BLOCK_MASK;
+
+ if (split == 1) {
+ rc = _write((char *) header + 1, 1, rec->disk_start - 1);
+ if (rc < 0)
+ return rc;
+ rc = _write((char *) header, 1, prev_block_end);
+ if (rc < 0)
+ return rc;
+ return 0;
+ }
+
+ if (split == 2) {
+ rc = _write(header, sizeof(*header), prev_block_end - 1);
+ if (rc < 0)
+ return rc;
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Update the displacement of the last block if the block was spanned and
+ * the new end is inside the previously spanned block. The displacement
+ * must point after the last record to a null length header.
+ * If the block wasn't spanned the displacement of the trimmed record needs
+ * no update.
+ */
+static void adjust_displacement(struct file *f, int bnr, unsigned int disp)
+{
+ if (f->blist[bnr].disp == VAR_RECORD_SPANNED)
+ f->blist[bnr].disp = disp;
+}
+
+/*
+ * Split the last record if needed and wipe until block end.
+ * offset points to the last byte of the trimmed record that is
+ * not a line feed.
+ */
+static int trim_record(struct file *f, off_t offset, struct record *rec)
+{
+ int rc, wipe_off, wipe_len, free = 0;
+ off_t file_start = rec->file_start;
+ struct record_ext *rext;
+ u16 header;
+
+ BUG(!offset_in_record(offset, rec));
+
+ if (offset >= rec->file_start &&
+ offset < rec->file_start + rec->first_block_len) {
+ wipe_off = offset + 1 - rec->file_start;
+ wipe_len = cmsfs.blksize - ((rec->disk_start & DATA_BLOCK_MASK) + wipe_off);
+ if (!wipe_len)
+ goto ext;
+ if (rec->disk_start) {
+ rc = _zero(rec->disk_start + wipe_off, wipe_len);
+ BUG(rc < 0);
+ }
+
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ adjust_displacement(f, rec->block_nr,
+ (rec->disk_start + wipe_off) & DATA_BLOCK_MASK);
+ free = 1;
+ }
+
+ext:
+ if (rec->ext == NULL)
+ goto header;
+
+ file_start += rec->first_block_len;
+ rext = rec->ext;
+ do {
+ if (free) {
+ free_block(rext->disk_start);
+ f->fst->nr_blocks--;
+ if (!rext->disk_start && f->fst->record_format == RECORD_LEN_FIXED)
+ f->nr_null_blocks--;
+ } else {
+ if (offset >= file_start &&
+ offset < file_start + rext->len) {
+ wipe_off = offset + 1 - file_start;
+ wipe_len = cmsfs.blksize - ((rec->disk_start & DATA_BLOCK_MASK) + wipe_off);
+ if (!wipe_len)
+ continue;
+ if (rext->disk_start) {
+ rc = _zero(rext->disk_start + wipe_off, wipe_len);
+ BUG(rc < 0);
+ }
+
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ adjust_displacement(f, rext->block_nr,
+ (rext->disk_start + wipe_off) & DATA_BLOCK_MASK);
+ free = 1;
+ }
+ }
+ file_start += rext->len;
+ rext = rext->next;
+ } while (rext != NULL);
+
+header:
+ /* update variable record header with new record length */
+ if (f->fst->record_format == RECORD_LEN_VARIABLE) {
+ header = offset + 1 - rec->file_start;
+ rc = update_var_header_len(f, &header, rec);
+ if (rc < 0)
+ return rc;
+
+ /* update total_len in rlist, needed to recalculate lrecl */
+ rec->total_len = header;
+ }
+ return 0;
+}
+
+/*
+ * Update levels count.
+ */
+static void update_levels(struct file *f)
+{
+ int per_block = f->ptr_per_block;
+ int levels = 1, blocks = f->fst->nr_blocks;
+
+ if (blocks < 2) {
+ f->fst->levels = 0;
+ return;
+ }
+
+ while (blocks / (per_block + 1)) {
+ levels++;
+ blocks /= per_block;
+ }
+
+ f->fst->levels = levels;
+}
+
+/*
+ * Called by write only using the cached value.
+ */
+static void update_lrecl_fast(struct file *f, int rlen)
+{
+ if (rlen > (int) f->fst->record_len)
+ f->fst->record_len = rlen;
+}
+
+/*
+ * Update longest record length for variable files.
+ */
+static void update_lrecl(struct file *f)
+{
+ unsigned int lrecl = 0;
+ struct record *rec;
+ int i;
+
+ if (f->fst->record_format == RECORD_LEN_FIXED)
+ return;
+
+ if (!f->fst->nr_records) {
+ f->fst->record_len = 0;
+ return;
+ }
+
+ for (i = 0; i < f->fst->nr_records; i++) {
+ rec = get_record(f, i);
+ if (rec->total_len > lrecl)
+ lrecl = rec->total_len;
+ }
+ f->fst->record_len = lrecl;
+}
+
+static int shrink_file(struct file *f, off_t size)
+{
+ struct record *new_end_rec, *end_rec;
+ int rlen = f->fst->record_len;
+ off_t offset = size;
+ int new_end_nr, rc;
+
+ /* truncate MUST be aligned to record length for fixed files */
+ if (f->fst->record_format == RECORD_LEN_FIXED) {
+ if (f->linefeed)
+ rlen++;
+ if (size % rlen)
+ return -EINVAL;
+ }
+
+ if (size == 0) {
+ new_end_nr = -1;
+ new_end_rec = NULL;
+ goto free;
+ }
+
+ /*
+ * offset may point to the linefeed after a record, let it point to the
+ * last byte of the new last record instead. The linefeed is virtual
+ * and will be generated automatically.
+ */
+ if (f->linefeed) {
+ new_end_rec = find_record(f, offset - 1, &new_end_nr);
+ if (new_end_rec == LINEFEED_OFFSET)
+ offset--;
+ }
+
+ /* get the new last record of the file */
+ new_end_rec = find_record(f, offset - 1, &new_end_nr);
+ BUG(new_end_rec == NULL || new_end_rec == LINEFEED_OFFSET);
+
+free:
+ /* free from the end until new_end_rec */
+ while (f->fst->nr_records - 1 > new_end_nr) {
+ /* get the currently last record of the file */
+ end_rec = get_record(f, f->fst->nr_records - 1);
+ free_record(f, end_rec);
+ }
+
+ if (new_end_rec != NULL) {
+ rc = trim_record(f, offset - 1, new_end_rec);
+ if (rc < 0)
+ return rc;
+ }
+
+ f->session_size = size;
+ if (f->fst->fop)
+ f->fops->delete_pointers(f, f->fst->levels, ABS(f->fst->fop));
+
+ update_levels(f);
+ update_lrecl(f);
+ rc = rewrite_pointers(f);
+ if (rc < 0)
+ return rc;
+ update_block_count();
+ return 0;
+}
+
+static void hide_null_blocks(struct file *f)
+{
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ return;
+ f->fst->nr_blocks -= f->nr_null_blocks;
+}
+
+static void unhide_null_blocks(struct file *f)
+{
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ return;
+ f->fst->nr_blocks += f->nr_null_blocks;
+}
+
+static void update_fst(struct file *f, off_t addr)
+{
+ int rc;
+
+ hide_null_blocks(f);
+ rc = _write(f->fst, sizeof(*f->fst), addr);
+ BUG(rc < 0);
+ unhide_null_blocks(f);
+}
+
+static int cmsfs_truncate(const char *path, off_t size)
+{
+ struct fst_entry fst;
+ off_t fst_addr;
+ struct file *f;
+ ssize_t len;
+ int rc = 0;
+
+ if (cmsfs.readonly)
+ return -EROFS;
+
+ /*
+ * If file is opened and modified disk content may be obsolete.
+ * Must use the file object to get the current version of the file.
+ */
+ f = file_open(path + 1);
+ if (f != NULL) {
+ fst_addr = f->fst_addr;
+ len = f->session_size;
+ } else {
+ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED);
+ if (!fst_addr)
+ return -ENOENT;
+ len = get_file_size_logical(&fst);
+ if (len < 0)
+ return -EIO;
+ f = create_file_object(&fst, &rc);
+ if (f == NULL)
+ return rc;
+ }
+
+ if (len == size)
+ return 0;
+ if (size < len) {
+ rc = shrink_file(f, size);
+ if (rc != 0)
+ return rc;
+ } else
+ return -EINVAL;
+
+ rc = set_fst_date_current(f->fst);
+ if (rc != 0)
+ return rc;
+
+ update_fst(f, fst_addr);
+ if (!f->use_count)
+ destroy_file_object(f);
+ return rc;
+}
+
+#ifdef HAVE_SETXATTR
+static int cmsfs_setxattr(const char *path, const char *name, const char *value,
+ size_t size, int flags)
+{
+ struct fst_entry fst;
+ off_t fst_addr;
+ int mode_n, rc;
+ long int rlen;
+ char mode_l;
+ char *in;
+
+ /* meaningless since our xattrs are virtual and not stored on disk */
+ (void) flags;
+
+ if (cmsfs.readonly)
+ return -EROFS;
+
+ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED);
+ if (!fst_addr)
+ return -ENOENT;
+
+ if (strcmp(name, xattr_format.name) == 0) {
+ /* only allowed for empty files */
+ if (fst.nr_records != 0)
+ return -EINVAL;
+ if (size != xattr_format.size)
+ return -ERANGE;
+ if (*value == 'F') {
+ fst.record_format = RECORD_LEN_FIXED;
+ fst.ptr_size = 0x4;
+ } else if (*value == 'V') {
+ fst.record_format = RECORD_LEN_VARIABLE;
+ fst.ptr_size = 0xc;
+ } else
+ return -EINVAL;
+ goto write;
+ }
+
+ if (strcmp(name, xattr_lrecl.name) == 0) {
+ /* done by fs for variable files */
+ if (fst.record_format == RECORD_LEN_VARIABLE)
+ return -EINVAL;
+ /* only allowed for empty files */
+ if (fst.nr_records != 0)
+ return -EINVAL;
+ if (size > xattr_lrecl.size)
+ return -ERANGE;
+ in = calloc(size + 1, 1);
+ if (in == NULL)
+ return -ENOMEM;
+ memcpy(in, value, size);
+ errno = 0;
+ rlen = strtol(in, (char **) NULL, 10);
+ free(in);
+ if (errno != 0 || rlen == 0 || rlen > MAX_RECORD_LEN)
+ return -EINVAL;
+ fst.record_len = rlen;
+ goto write;
+ }
+
+ if (strcmp(name, xattr_mode.name) == 0) {
+ if (size != xattr_mode.size)
+ return -ERANGE;
+ mode_l = value[0];
+ if (mode_l < 'A' || mode_l > 'Z')
+ return -EINVAL;
+ mode_n = atoi(&value[1]);
+ if (!isdigit(value[1]) || mode_n < 0 || mode_n > 6)
+ return -EINVAL;
+ ebcdic_enc((char *) &fst.mode, value, sizeof(fst.mode));
+ goto write;
+ }
+ return -ENODATA;
+
+write:
+ rc = _write(&fst, sizeof(fst), fst_addr);
+ BUG(rc < 0);
+ return 0;
+}
+
+static int cmsfs_getxattr(const char *path, const char *name, char *value,
+ size_t size)
+{
+ char buf[xattr_lrecl.size + 1];
+ struct fst_entry fst;
+
+ /* nothing for root directory but clear error code needed */
+ if (strcmp(path, "/") == 0)
+ return -ENODATA;
+
+ if (!lookup_file(path + 1, &fst, HIDE_UNLINKED))
+ return -ENOENT;
+
+ /* null terminate strings */
+ memset(value, 0, size);
+
+ /* format */
+ if (strcmp(name, xattr_format.name) == 0) {
+ if (size == 0)
+ return xattr_format.size;
+ if (size < xattr_format.size)
+ return -ERANGE;
+ if (fst.record_format == RECORD_LEN_FIXED)
+ *value = 'F';
+ else if (fst.record_format == RECORD_LEN_VARIABLE)
+ *value = 'V';
+ return xattr_format.size;
+ }
+
+ /* lrecl */
+ if (strcmp(name, xattr_lrecl.name) == 0) {
+ if (size == 0)
+ return xattr_lrecl.size;
+ if (size < xattr_lrecl.size)
+ return -ERANGE;
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), "%d", fst.record_len);
+ memcpy(value, buf, strlen(buf));
+ return strlen(buf);
+ }
+
+ /* mode */
+ if (strcmp(name, xattr_mode.name) == 0) {
+ if (size == 0)
+ return xattr_mode.size;
+ if (size < xattr_mode.size)
+ return -ERANGE;
+ ebcdic_dec(value, (char *) &fst.mode, 2);
+ return xattr_mode.size;
+ }
+ return -ENODATA;
+}
+
+static int cmsfs_listxattr(const char *path, char *list, size_t size)
+{
+ struct fst_entry fst;
+ size_t list_len;
+ int pos = 0;
+
+ if (!lookup_file(path + 1, &fst, HIDE_UNLINKED))
+ return -ENOENT;
+
+ list_len = strlen(xattr_format.name) + 1 +
+ strlen(xattr_lrecl.name) + 1 +
+ strlen(xattr_mode.name);
+ if (!size)
+ return list_len;
+ if (size < list_len)
+ return -ERANGE;
+
+ strcpy(list, xattr_format.name);
+ pos += strlen(xattr_format.name) + 1;
+ strcpy(&list[pos], xattr_lrecl.name);
+ pos += strlen(xattr_lrecl.name) + 1;
+ strcpy(&list[pos], xattr_mode.name);
+ pos += strlen(xattr_mode.name) + 1;
+ return pos;
+}
+#endif /* HAVE_SETXATTR */
+
+/*
+ * Return the number of unused bytes in the last block.
+ */
+static int examine_last_block(struct file *f)
+{
+ struct record_ext *rext;
+ struct record *rec;
+ int last_bnr, len;
+ off_t start;
+
+ if (!f->fst->nr_blocks || !f->fst->nr_records)
+ return 0;
+
+ last_bnr = f->fst->nr_blocks - 1;
+ rec = &f->rlist[f->fst->nr_records - 1];
+
+ /* last block may be an extension */
+ if (rec->block_nr == last_bnr) {
+ start = rec->disk_start;
+ len = rec->first_block_len;
+ goto out;
+ }
+
+ /* if write is split and exactly the extension not yet started */
+ if (rec->ext == NULL)
+ return 0;
+
+ rext = rec->ext;
+ do {
+ if (rext->block_nr == last_bnr) {
+ start = rext->disk_start;
+ len = rext->len;
+ goto out;
+ }
+ rext = rext->next;
+ } while (rext != NULL);
+
+ return 0;
+out:
+ if (start == NULL_BLOCK)
+ start = f->null_ctr;
+ return ((start | DATA_BLOCK_MASK) + 1) - (start + len);
+}
+
+static int get_record_len(struct file *f, size_t size)
+{
+ int chunk = 0;
+
+ if (f->fst->record_format == RECORD_LEN_FIXED) {
+ if (!f->fst->record_len) {
+ chunk = (size > MAX_RECORD_LEN) ?
+ MAX_RECORD_LEN : size;
+ f->fst->record_len = chunk;
+ } else
+ chunk = f->fst->record_len;
+
+ if (chunk > MAX_RECORD_LEN)
+ return -EINVAL;
+ if (size < (size_t) chunk)
+ chunk = size;
+ } else if (f->fst->record_format == RECORD_LEN_VARIABLE) {
+ chunk = size;
+ if (chunk > MAX_RECORD_LEN)
+ chunk = MAX_RECORD_LEN;
+ }
+ return chunk;
+}
+
+/*
+ * Get the disk address of the first byte after the last record.
+ */
+static off_t disk_end(struct file *f)
+{
+ struct record_ext *rext;
+ struct record *rec;
+
+ if (!f->fst->nr_records)
+ return 0;
+
+ rec = &f->rlist[f->fst->nr_records - 1];
+ if (rec->ext == NULL) {
+ if (rec->disk_start == NULL_BLOCK)
+ return 0;
+ else
+ return rec->disk_start + rec->first_block_len;
+ }
+
+ rext = rec->ext;
+ while (rext->next != NULL)
+ rext = rext->next;
+ if (rext->disk_start == NULL_BLOCK)
+ return 0;
+ else
+ return rext->disk_start + rext->len;
+}
+
+/*
+ * Store the displacement for the first write to a new block.
+ * nr_blocks already contains the new block. If the block is completely filled
+ * the displacement is spanned, also if one header byte is used
+ * since the header belongs to the record regarding the
+ * displacement but not if both header bytes are on the block.
+ */
+void store_displacement(struct file *f, int disp)
+{
+ f->blist[f->fst->nr_blocks - 1].disp = disp;
+ f->vrstate->block_state = BWS_BLOCK_USED;
+}
+
+/*
+ * Allocate a new block if needed, set write pointer and return the number
+ * of bytes available on the block.
+ */
+static int write_prepare_block(struct file *f, int null_block, ssize_t size)
+{
+ int len, new_block = 0;
+
+ if (null_block) {
+ /*
+ * TODO: need to write header before killing write_ptr for
+ * sparse files.
+ */
+ BUG(f->fst->record_format == RECORD_LEN_VARIABLE);
+
+ f->write_ptr = 0;
+ /* new null block started ? */
+ if (f->null_ctr % cmsfs.blksize == 0) {
+ new_block = 1;
+ len = cmsfs.blksize;
+
+ /*
+ * Prevent allocating a null block if the block would
+ * not be complete. Use a normal block instead.
+ */
+ if (size < cmsfs.blksize) {
+ f->write_ptr = get_zero_block();
+ if (f->write_ptr < 0)
+ return f->write_ptr;
+ }
+
+ } else
+ len = ((f->null_ctr | DATA_BLOCK_MASK) + 1) - f->null_ctr;
+ } else {
+ if (f->write_ptr % cmsfs.blksize == 0) {
+ /*
+ * For fixed files use a different padding in text
+ * mode to pad records with spaces.
+ */
+ if (f->fst->record_format == RECORD_LEN_FIXED &&
+ f->translate)
+ f->write_ptr = get_filled_block();
+ else
+ f->write_ptr = get_zero_block();
+ if (f->write_ptr < 0)
+ return f->write_ptr;
+ new_block = 1;
+ len = cmsfs.blksize;
+ } else
+ len = ((f->write_ptr | DATA_BLOCK_MASK) + 1) - f->write_ptr;
+ }
+
+ if (new_block) {
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ f->vrstate->block_state = BWS_BLOCK_NEW;
+ f->blist[f->fst->nr_blocks].disk_addr = f->write_ptr;
+
+ if (!f->write_ptr && f->fst->record_format == RECORD_LEN_FIXED)
+ f->nr_null_blocks++;
+
+ f->fst->nr_blocks++;
+ }
+ return len;
+}
+
+/*
+ * Write variable record length header and return number of written bytes.
+ */
+static int write_var_header(struct file *f, int len, u16 vheader)
+{
+ u8 half_vheader;
+ int rc;
+
+ if (f->vrstate->record_state == RWS_HEADER_COMPLETE ||
+ f->vrstate->record_state == RWS_RECORD_INCOMPLETE)
+ return 0;
+
+ if (f->vrstate->record_state == RWS_HEADER_STARTED) {
+ /* write secord header byte */
+ half_vheader = vheader & 0xff;
+ rc = _write(&half_vheader, 1, f->write_ptr);
+ if (rc < 0)
+ return rc;
+ f->write_ptr++;
+ f->vrstate->record_state = RWS_HEADER_COMPLETE;
+ return 1;
+ }
+
+ /* block cannot be spanned if a header starts on it */
+ if (f->vrstate->block_state == BWS_BLOCK_NEW)
+ store_displacement(f, f->write_ptr & DATA_BLOCK_MASK);
+
+ if (len >= 2) {
+ rc = _write(&vheader, 2, f->write_ptr);
+ if (rc < 0)
+ return rc;
+ f->write_ptr += 2;
+ f->vrstate->rlen = vheader;
+ f->vrstate->record_state = RWS_HEADER_COMPLETE;
+ return 2;
+ } else {
+ /* len = 1, write first header byte */
+ half_vheader = vheader >> 8;
+ rc = _write(&half_vheader, 1, f->write_ptr);
+ if (rc < 0)
+ return rc;
+ f->write_ptr++;
+ f->vrstate->rlen = vheader;
+ f->vrstate->record_state = RWS_HEADER_STARTED;
+ return 1;
+ }
+}
+
+static int extend_block_fixed(struct file *f, const char *buf, int len,
+ size_t size, int rlen)
+{
+ int rc;
+
+ (void) rlen;
+
+ if (size < (size_t) len)
+ len = size;
+ if (f->write_ptr) {
+ rc = _write(buf, len, f->write_ptr);
+ if (rc < 0)
+ return rc;
+ f->write_ptr += len;
+ }
+ return len;
+}
+
+static int extend_block_variable(struct file *f, const char *buf, int len,
+ size_t size, int rlen)
+{
+ int rc, copied = 0, vh_len = 0, max = cmsfs.blksize;
+
+ if (!f->write_ptr)
+ return len;
+
+ while (len > 0) {
+ /* record may be party written already */
+ if (size < (size_t) rlen)
+ rlen = size;
+
+ vh_len = write_var_header(f, len, rlen);
+ if (vh_len < 0)
+ return vh_len;
+ len -= vh_len;
+ if (!len)
+ return copied;
+ /* record does not fit on block */
+ if (len < rlen)
+ rlen = len;
+ /* remaining record data less than block len */
+ if (f->vrstate->rlen < rlen)
+ rlen = f->vrstate->rlen;
+ rc = _write(buf, rlen, f->write_ptr);
+ if (rc < 0)
+ return rc;
+
+ f->write_ptr += rlen;
+
+ if (f->vrstate->block_state == BWS_BLOCK_NEW) {
+ /*
+ * If the second byte of a split header was written
+ * (blocksize - 1) is enough to make the block spanned.
+ */
+ if (vh_len == 1)
+ max--;
+ if (rlen >= max)
+ store_displacement(f, VAR_RECORD_SPANNED);
+ else
+ store_displacement(f, f->write_ptr & DATA_BLOCK_MASK);
+ }
+
+
+ copied += rlen;
+ size -= rlen;
+ len -= rlen;
+ f->vrstate->rlen -= rlen;
+
+ BUG(f->vrstate->rlen < 0);
+ if (!f->vrstate->rlen)
+ f->vrstate->record_state = RWS_RECORD_COMPLETE;
+
+ DEBUG("%s: wrote %d record bytes\n", __func__, rlen);
+ if (size <= 0)
+ return copied;
+ }
+
+ /* record is not yet finished */
+ f->vrstate->record_state = RWS_RECORD_INCOMPLETE;
+ return copied;
+}
+
+/*
+ * Extend an existing block or write data on a new block.
+ *
+ * size: requestes bytes to write to disk
+ * rlen: projected record len
+ * len: bytes left on the block
+ */
+static int extend_block(struct file *f, const char *buf, size_t size, int rlen)
+{
+ int len = write_prepare_block(f, (buf == NULL) ? 1 : 0, size);
+
+ if (len < 0)
+ return -ENOSPC;
+ BUG(!len);
+ return f->fops->write_data(f, buf, len, size, rlen);
+}
+
+/*
+ * Delete the record data from rlist and free extensions.
+ */
+static void delete_record(struct file *f, int nr)
+{
+ struct record *rec = &f->rlist[nr];
+ struct record_ext *tmp, *rext = rec->ext;
+
+ memset(rec, 0, sizeof(struct record));
+ while (rext != NULL) {
+ tmp = rext->next;
+ free(rext);
+ rext = tmp;
+ }
+}
+
+/*
+ * Update records from a start record to the end. The start record is one less
+ * than the previous last record since the previous last record may be
+ * incomplete.
+ */
+static int update_records(struct file *f, int nrecords)
+{
+ int i, rc, rnr, bnr = 0, skip = 0;
+ size_t total = 0;
+
+ rnr = f->fst->nr_records - 1;
+ if (rnr >= 0) {
+ total = f->rlist[rnr].file_start;
+ if (f->linefeed && total)
+ total--;
+ bnr = f->rlist[rnr].block_nr;
+ skip = f->rlist[rnr].disk_start & DATA_BLOCK_MASK;
+
+ /* skip must point before a variable header */
+ if (f->fst->record_format == RECORD_LEN_VARIABLE) {
+ if (skip >= VAR_RECORD_HEADER_SIZE)
+ skip -= VAR_RECORD_HEADER_SIZE;
+ else if (skip == 1) {
+ bnr--;
+ skip = cmsfs.blksize - 1;
+ } else if (skip == 0 && f->rlist[rnr].disk_start) {
+ bnr--;
+ skip = cmsfs.blksize - 2;
+ }
+ }
+ delete_record(f, rnr);
+ rnr--;
+ }
+
+ if (rnr < -1) {
+ rnr = -1;
+ skip = 0;
+ total = 0;
+ bnr = 0;
+ }
+
+ f->fst->nr_records += nrecords;
+ f->record_scan_state = RSS_DATA_BLOCK_STARTED;
+ for (i = bnr; i < f->fst->nr_blocks; i++) {
+ if (f->fst->record_format == RECORD_LEN_FIXED)
+ cache_fixed_data_block(f, f->blist[i].disk_addr + skip,
+ &bnr, &rnr, &total, skip);
+ else {
+ rc = cache_variable_data_block(f,
+ f->blist[i].disk_addr + skip,
+ &bnr, &rnr, f->blist[i].disp, &total, skip);
+ if (rc < 0)
+ return rc;
+ }
+ skip = 0;
+ }
+ return 0;
+}
+
+/*
+ * Calculate the number of new records.
+ */
+static int new_records(struct file *f, size_t size, int rlen)
+{
+ double tmp = size;
+ int len;
+
+ if (f->fst->record_format == RECORD_LEN_FIXED) {
+ len = f->fst->record_len;
+ if (f->linefeed)
+ len++;
+
+ /* need to fill a previously started record first */
+ if (f->session_size &&
+ f->session_size % len)
+ tmp = tmp - (len - (f->session_size % len));
+ }
+
+ if (tmp <= 0)
+ return 0;
+
+ tmp = ceil(tmp / rlen);
+ return (int) tmp;
+}
+
+/*
+ * Calculate number of new blocks.
+ */
+static int new_blocks(struct file *f, size_t size, int last, int nrecords)
+{
+ int last_usable = last;
+ double tmp = size;
+
+ /* subtract header bytes */
+ if (last_usable && f->fst->record_format == RECORD_LEN_VARIABLE) {
+ if (last_usable == 1)
+ last_usable = 0;
+ else
+ last_usable -= VAR_RECORD_HEADER_SIZE;
+ }
+
+ if ((int) size <= last_usable)
+ return 0;
+
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ tmp += nrecords * VAR_RECORD_HEADER_SIZE;
+
+ tmp -= last;
+ if (tmp <= 0)
+ return 0;
+
+ tmp = ceil(tmp / cmsfs.blksize);
+ return (int) tmp;
+}
+
+/*
+ * Increase record list count.
+ */
+static void resize_rlist(struct file *f, int new)
+{
+ if (!new)
+ return;
+
+ f->rlist = realloc(f->rlist, (f->fst->nr_records + new) *
+ sizeof(struct record));
+ if (f->rlist == NULL)
+ DIE_PERROR("realloc failed");
+ memset(&f->rlist[f->fst->nr_records], 0,
+ new * sizeof(struct record));
+}
+
+/*
+ * Increase block list count.
+ */
+static void resize_blist(struct file *f, int new)
+{
+ if (!new)
+ return;
+
+ f->blist = realloc(f->blist, (f->fst->nr_blocks + new) *
+ sizeof(struct block));
+ if (f->blist == NULL)
+ DIE_PERROR("realloc failed");
+ memset(&f->blist[f->fst->nr_blocks], 0,
+ new * sizeof(struct block));
+}
+
+/*
+ * Reserve blocks for meta data (pointer blocks) of a file so that
+ * blocks for meta-data are available if the disk runs full.
+ */
+static void reserve_meta_blocks(struct file *f, int old, int new, int level)
+{
+ double nentries, oentries;
+ int newp;
+
+ if (!new)
+ return;
+
+ newp = pointers_per_level(f, level, old + new);
+ oentries = pointers_per_level(f, level, old);
+
+ oentries = ceil(oentries / f->ptr_per_block);
+ nentries = newp;
+ nentries = ceil(nentries / f->ptr_per_block);
+
+ cmsfs.reserved_blocks += nentries - oentries;
+
+ if (newp > f->ptr_per_block)
+ reserve_meta_blocks(f, oentries, nentries, ++level);
+}
+
+/*
+ * Update the max record number in the last pointer block per level.
+ */
+static int update_last_block_vptr(struct file *f, off_t addr, int level,
+ struct var_ptr *vptr)
+{
+ int last, rc;
+
+ if (!level)
+ return 0;
+ level--;
+
+ /* read offset pointer from the end of the var pointer block */
+ rc = _read(&last, sizeof(last), addr + cmsfs.blksize - sizeof(last));
+ if (rc < 0)
+ return rc;
+
+ if (last % sizeof(*vptr) || last > cmsfs.blksize)
+ return -EIO;
+ rc = _read(vptr, sizeof(*vptr), addr + last);
+ if (rc < 0)
+ return rc;
+
+ /* update max record number */
+ vptr->hi_record_nr = f->fst->nr_records;
+ rc = _write(vptr, sizeof(*vptr), addr + last);
+ if (rc < 0)
+ return rc;
+
+ if (!level)
+ return 0;
+ if (vptr->next == NULL_BLOCK)
+ return 0;
+ return update_last_block_vptr(f, ABS(vptr->next), level, vptr);
+}
+
+/*
+ * Append records at current file end. If buf is NULL write zero bytes.
+ */
+static int write_append(struct file *f, const char *buf, size_t size)
+{
+ int rc, i, nrecords, nblocks, last, len, copied = 0;
+ int rlen = get_record_len(f, size);
+
+ if (rlen < 0)
+ return rlen;
+ nrecords = new_records(f, size, rlen);
+
+ /* get last block unused bytes for block count */
+ last = examine_last_block(f);
+
+ /* initialize write_ptr once */
+ f->write_ptr = disk_end(f);
+
+ nblocks = new_blocks(f, size, last, nrecords);
+ if (nblocks > 0)
+ f->ptr_dirty = 1;
+
+ resize_rlist(f, nrecords);
+ resize_blist(f, nblocks);
+ if (f->fst->nr_blocks + nblocks > 1)
+ reserve_meta_blocks(f, f->fst->nr_blocks, nblocks, 1);
+
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ f->vrstate->record_state = RWS_RECORD_COMPLETE;
+
+ /* first use existing last block */
+ if (last) {
+ len = extend_block(f, buf, size, rlen);
+ if (len < 0)
+ return len;
+ copied += len;
+ size -= len;
+ if (buf != NULL)
+ buf += len;
+ }
+
+ for (i = 0; i < nblocks; i++) {
+ len = extend_block(f, buf, size, rlen);
+ if (len < 0) {
+ if (copied > 0)
+ goto out;
+ else
+ return len;
+ }
+ copied += len;
+ size -= len;
+ if (buf != NULL)
+ buf += len;
+ DEBUG("%s: wrote: %d bytes\n", __func__, copied);
+ }
+out:
+ rc = update_records(f, nrecords);
+ if (rc < 0)
+ return rc;
+ if (f->fst->record_format == RECORD_LEN_VARIABLE)
+ update_lrecl_fast(f, rlen);
+ return copied;
+}
+
+static int do_write(struct file *f, const char *buf, size_t size, off_t offset)
+{
+ ssize_t len, copied = 0;
+ struct var_ptr vptr;
+ int rc;
+
+ if (!size)
+ return 0;
+
+ len = f->session_size;
+ if (f->linefeed)
+ len -= f->fst->nr_records;
+ BUG(len < 0);
+
+ if (offset < len)
+ return -EINVAL;
+
+ /*
+ * Writes with null blocks (sparse files) may be prevented by tools
+ * which call lseek instead. Since we don't implement lseek fuse may
+ * call us with an offset after file. We don't support sparse file
+ * writes currently.
+ */
+ if (offset > len)
+ return -EINVAL;
+
+ copied = write_append(f, buf, size);
+ if (copied <= 0)
+ return copied;
+ f->session_size += copied;
+
+ /* add linefeed byte */
+ if (f->linefeed)
+ f->session_size++;
+
+ if (f->ptr_dirty) {
+ f->old_levels = f->fst->levels;
+ update_levels(f);
+
+ if (f->fst->fop)
+ f->fops->delete_pointers(f, f->old_levels,
+ ABS(f->fst->fop));
+ rc = rewrite_pointers(f);
+ if (rc < 0)
+ return rc;
+ f->ptr_dirty = 0;
+ } else
+ if (f->fst->levels > 0) {
+ rc = update_last_block_vptr(f, ABS(f->fst->fop),
+ f->fst->levels, &vptr);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = set_fst_date_current(f->fst);
+ if (rc != 0)
+ return rc;
+
+ update_fst(f, f->fst_addr);
+ set_fdir_date_current();
+ update_block_count();
+ return copied;
+}
+
+static void cache_write_data(struct file *f, const char *buf, int len)
+{
+ if (f->wcache_used + len > WCACHE_MAX)
+ len = WCACHE_MAX - f->wcache_used;
+ if (buf == NULL)
+ memset(&f->wcache[f->wcache_used], FILLER_ASCII, len);
+ else
+ memcpy(&f->wcache[f->wcache_used], buf, len);
+ f->wcache_used += len;
+}
+
+static void purge_wcache(struct file *f)
+{
+ f->wcache_used = 0;
+ f->wcache_commited = 0;
+}
+
+/*
+ * Scan for the next newline character and return the number of bytes in
+ * this record.
+ */
+static ssize_t find_newline(const char *buf, int len)
+{
+ char *pos;
+
+ pos = memchr(buf, LINEFEED_ASCII, len);
+ if (pos == NULL)
+ return LINEFEED_NOT_FOUND;
+ else
+ return pos - buf;
+}
+
+static int cmsfs_write(const char *path, const char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ int scan_len = min(size, MAX_RECORD_LEN + 1);
+ int rc, nl_byte = 1, null_record = 0, pad = 0;
+ struct file *f = get_fobj(fi);
+ ssize_t rsize;
+
+ (void) path;
+
+ if (cmsfs.readonly)
+ return -EROFS;
+
+ if (!f->linefeed)
+ return do_write(f, buf, size, offset);
+
+ /* remove already comitted bytes */
+ offset -= f->wcache_used;
+
+ /* write offset must be at the end of the file */
+ if (offset + f->null_records + f->pad_bytes != f->session_size)
+ return -EINVAL;
+
+ if (f->fst->record_format == RECORD_LEN_FIXED &&
+ f->fst->record_len)
+ scan_len = min(scan_len, f->fst->record_len + 1);
+
+ rsize = find_newline(buf, scan_len);
+ BUG(rsize < LINEFEED_NOT_FOUND);
+
+ if (rsize == LINEFEED_NOT_FOUND) {
+ if (f->wcache_used + scan_len >= WCACHE_MAX) {
+ purge_wcache(f);
+ return -EINVAL;
+ } else {
+ if (f->fst->record_format == RECORD_LEN_FIXED &&
+ f->wcache_commited + scan_len >= f->fst->record_len) {
+ purge_wcache(f);
+ return -EINVAL;
+ }
+ cache_write_data(f, buf, scan_len);
+ f->wcache_commited += scan_len;
+ return scan_len;
+ }
+ }
+
+ cache_write_data(f, buf, rsize);
+
+ if (f->fst->record_format == RECORD_LEN_FIXED &&
+ f->wcache_used < f->fst->record_len) {
+ pad = f->fst->record_len - f->wcache_used;
+ cache_write_data(f, NULL, pad);
+ }
+
+ /* translate */
+ rc = convert_text(cmsfs.iconv_to, f->wcache, f->wcache_used);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Note: empty records are forbidden by design. CMS converts
+ * an empty record to a single space. Follow that convention.
+ */
+ if (!f->wcache_used) {
+ *f->wcache = FILLER_EBCDIC;
+ f->wcache_used = 1;
+ nl_byte = 0;
+ null_record = 1;
+ }
+
+ /* correct file offset by removing the virtual linefeeds */
+ offset -= (f->fst->nr_records - f->null_records);
+ offset += f->pad_bytes;
+ BUG(offset < 0);
+
+ rc = do_write(f, f->wcache, f->wcache_used, offset);
+ if (rc < 0)
+ return rc;
+
+ rc += nl_byte;
+ if (null_record)
+ f->null_records++;
+ rc -= f->wcache_commited;
+ rc -= pad;
+ BUG(rc < 0);
+ purge_wcache(f);
+ f->pad_bytes += pad;
+ return rc;
+}
+
+/*
+ * Get the address of the last directory entry.
+ */
+off_t find_last_fdir_entry(off_t addr, int level)
+{
+ struct fst_entry fst;
+ int left, rc;
+ off_t ptr;
+
+ if (level > 0) {
+ level--;
+ left = PTRS_PER_BLOCK;
+ while (left--) {
+ ptr = get_fixed_pointer(addr + left * PTR_SIZE);
+ BUG(ptr < 0);
+ if (ptr)
+ return find_last_fdir_entry(ptr, level);
+ }
+ DIE("Directory entry not found\n");
+ return 0;
+ }
+
+ left = cmsfs.blksize / sizeof(struct fst_entry);
+ while (left--) {
+ rc = _read(&fst, sizeof(fst),
+ addr + left * sizeof(struct fst_entry));
+ BUG(rc < 0);
+ if (is_file((unsigned long long *) fst.name,
+ (unsigned long long *) fst.type))
+ return addr + left * sizeof(struct fst_entry);
+ }
+ DIE("Directory entry not found\n");
+}
+
+static int delete_file(const char *path)
+{
+ off_t fst_kill, fst_last;
+ struct walk_file walk;
+ struct file *f_moved;
+ char file[MAX_FNAME];
+ struct fst_entry fst;
+ struct file *f;
+ int rc = 0, i;
+
+ if (cmsfs.readonly)
+ return -EROFS;
+
+ fst_kill = lookup_file(path + 1, &fst, SHOW_UNLINKED);
+ if (!fst_kill)
+ return -ENOENT;
+ f = create_file_object(&fst, &rc);
+ if (f == NULL)
+ return rc;
+
+ /* delete all data blocks */
+ for (i = 0; i < f->fst->nr_blocks; i++)
+ free_block(f->blist[i].disk_addr);
+
+ if (f->fst->fop) {
+ rc = f->fops->delete_pointers(f, f->fst->levels, ABS(f->fst->fop));
+ if (rc < 0)
+ goto error;
+ }
+
+ if (cmsfs.dir_levels)
+ fst_last = find_last_fdir_entry(get_fop(cmsfs.fdir), cmsfs.dir_levels);
+ else
+ fst_last = find_last_fdir_entry(cmsfs.fdir, cmsfs.dir_levels);
+
+ /* remove unlinked file from fcache */
+ strncpy(file, path + 1, MAX_FNAME);
+ str_toupper(file);
+ invalidate_htab_entry(file);
+
+ if (fst_last == fst_kill)
+ goto skip_copy;
+
+ /* copy last entry over unlinked entry */
+ rc = _read(&fst, sizeof(struct fst_entry), fst_last);
+ BUG(rc < 0);
+ rc = _write(&fst, sizeof(struct fst_entry), fst_kill);
+ BUG(rc < 0);
+
+ /* update moved fcache entry */
+ memset(file, 0, sizeof(file));
+ decode_edf_name(file, fst.name, fst.type);
+ update_htab_entry(fst_kill, file);
+ /* update cached address of moved FST */
+ f_moved = file_open(file);
+ if (f_moved != NULL)
+ f->fst_addr = fst_kill;
+
+skip_copy:
+ /* delete last entry */
+ rc = _zero(fst_last, sizeof(struct fst_entry));
+ BUG(rc < 0);
+
+ /* if the deleted entry was the first of a block, free the block */
+ if (fst_last % cmsfs.blksize == 0) {
+ cache_dblocks(&walk);
+ /* delete the last block from dlist */
+ walk.dlist_used--;
+ free_block(fst_last);
+ purge_dblock_ptrs(cmsfs.dir_levels, get_fop(cmsfs.fdir));
+ rewrite_dblock_ptrs(&walk);
+ free_dblocks(&walk);
+ }
+
+ destroy_file_object(f);
+ decrease_file_count();
+ update_block_count();
+ return 0;
+
+error:
+ destroy_file_object(f);
+ return rc;
+}
+
+static int cmsfs_unlink(const char *path)
+{
+ struct fst_entry fst;
+ off_t fst_addr;
+ struct file *f;
+
+ if (cmsfs.readonly)
+ return -EROFS;
+
+ fst_addr = lookup_file(path + 1, &fst, HIDE_UNLINKED);
+ if (!fst_addr)
+ return -ENOENT;
+
+ f = file_open(path + 1);
+ if (f != NULL) {
+ f->unlinked = 1;
+ return 0;
+ }
+ return delete_file(path);
+}
+
+static int flush_wcache(struct file *f)
+{
+ off_t offset = f->session_size;
+ int rc;
+
+ /* translate */
+ rc = convert_text(cmsfs.iconv_to, f->wcache, f->wcache_used);
+ if (rc < 0)
+ return rc;
+
+ /* correct file offset by removing the virtual linefeeds */
+ offset -= (f->fst->nr_records - f->null_records);
+ BUG(offset < 0);
+
+ rc = do_write(f, f->wcache, f->wcache_used, offset);
+ purge_wcache(f);
+ f->null_records = 0;
+ if (rc < 0)
+ return rc;
+ return 0;
+}
+
+static int cmsfs_release(const char *path, struct fuse_file_info *fi)
+{
+ struct file *f = get_fobj(fi);
+ int rc = 0;
+
+ (void) path;
+
+ if (f == NULL) {
+ DEBUG("release internal error\n");
+ return -EINVAL;
+ }
+
+ if (fi->flags & O_RDWR || fi->flags & O_WRONLY) {
+ f->write_count--;
+ if (f->wcache_used)
+ rc = flush_wcache(f);
+ }
+
+ if (f->use_count == 1) {
+ if (f->unlinked)
+ delete_file(f->path);
+ list_del(&f->list);
+ destroy_file_object(f);
+ } else
+ f->use_count--;
+
+ fi->fh = 0;
+ return rc;
+}
+
+static void init_fops(struct file *f)
+{
+ if (f->fst->record_format == RECORD_LEN_FIXED)
+ f->fops = &fops_fixed;
+ else
+ f->fops = &fops_variable;
+}
+
+/*
+ * Create a file object to cache all needed file data.
+ * Note: the caller must ensure that the file exists.
+ */
+static struct file *create_file_object(struct fst_entry *fst, int *rc)
+{
+ struct file *f;
+
+ f = malloc(sizeof(*f));
+ if (f == NULL)
+ goto oom;
+ memset(f, 0, sizeof(*f));
+
+ f->fst = malloc(sizeof(struct fst_entry));
+ if (f->fst == NULL)
+ goto oom_f;
+
+ memcpy(f->fst, fst, sizeof(*fst));
+ workaround_nr_blocks(f);
+ init_fops(f);
+
+ f->linefeed = linefeed_mode_enabled(f->fst);
+ f->translate = f->linefeed;
+
+ f->record_scan_state = RSS_DATA_BLOCK_STARTED;
+
+ if (f->fst->record_format == RECORD_LEN_FIXED)
+ f->ptr_per_block = cmsfs.fixed_ptrs_per_block;
+ else
+ f->ptr_per_block = cmsfs.var_ptrs_per_block;
+
+ f->vrstate = malloc(sizeof(*f->vrstate));
+ if (f->vrstate == NULL)
+ goto oom_fst;
+ memset(f->vrstate, 0, sizeof(*f->vrstate));
+
+ /*
+ * Prevent calloc for zero records since it returns a pointer != NULL
+ * which causes trouble at free. Also don't call cache_file.
+ */
+ if (f->fst->nr_records == 0)
+ return f;
+
+ f->rlist = calloc(f->fst->nr_records, sizeof(struct record));
+ if (f->rlist == NULL)
+ goto oom_vrstate;
+
+ f->blist = calloc(f->fst->nr_blocks, sizeof(struct block));
+ if (f->blist == NULL)
+ goto oom_rlist;
+
+ *rc = cache_file(f);
+ if (*rc < 0)
+ goto error;
+ return f;
+
+error:
+ if (*rc == 0)
+ *rc = -ENOMEM;
+oom_rlist:
+ free(f->rlist);
+oom_vrstate:
+ free(f->vrstate);
+oom_fst:
+ free(f->fst);
+oom_f:
+ free(f);
+oom:
+ return NULL;
+}
+
+static void destroy_file_object(struct file *f)
+{
+ struct record_ext *rext, *tmp;
+ struct record *rec;
+ int i;
+
+ free(f->wcache);
+ free(f->vrstate);
+
+ for (i = 0; i < f->fst->nr_records; i++) {
+ rec = &f->rlist[i];
+ rext = rec->ext;
+ while (rext != NULL) {
+ tmp = rext->next;
+ free(rext);
+ rext = tmp;
+ }
+ }
+
+ free(f->rlist);
+ free(f->blist);
+ free(f->fst);
+ free(f);
+}
+
+static struct file_operations fops_fixed = {
+ .cache_data = cache_file_fixed,
+ .write_data = extend_block_fixed,
+ .delete_pointers = purge_pointer_block_fixed,
+ .write_pointers = rewrite_pointer_block_fixed,
+};
+
+static struct file_operations fops_variable = {
+ .cache_data = cache_file_variable,
+ .write_data = extend_block_variable,
+ .delete_pointers = purge_pointer_block_variable,
+ .write_pointers = rewrite_pointer_block_variable,
+};
+
+static struct fuse_operations cmsfs_oper = {
+ .getattr = cmsfs_getattr,
+ .statfs = cmsfs_statfs,
+ .readdir = cmsfs_readdir,
+ .open = cmsfs_open,
+ .release = cmsfs_release,
+ .read = cmsfs_read,
+ .utimens = cmsfs_utimens,
+ .rename = cmsfs_rename,
+ .fsync = cmsfs_fsync,
+ .truncate = cmsfs_truncate,
+ .create = cmsfs_create,
+ .write = cmsfs_write,
+ .unlink = cmsfs_unlink,
+#ifdef HAVE_SETXATTR
+ .listxattr = cmsfs_listxattr,
+ .getxattr = cmsfs_getxattr,
+ .setxattr = cmsfs_setxattr,
+ /* no removexattr since our xattrs are virtual */
+#endif
+};
+
+static int cmsfs_fuse_main(struct fuse_args *args,
+ struct fuse_operations *cmsfs_oper)
+{
+#if FUSE_VERSION >= 26
+ return fuse_main(args->argc, args->argv, cmsfs_oper, NULL);
+#else
+ return fuse_main(args->argc, args->argv, cmsfs_oper);
+#endif
+}
+
+static int cmsfs_process_args(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) data;
+
+ switch (key) {
+ case FUSE_OPT_KEY_OPT:
+ if (strcmp(arg, "allow_other") == 0)
+ cmsfs.allow_other = 1;
+ return 1;
+ case FUSE_OPT_KEY_NONOPT:
+ if (cmsfs.device == NULL) {
+ cmsfs.device = strdup(arg);
+ return 0;
+ }
+ return 1;
+ case KEY_HELP:
+ usage(outargs->argv[0]);
+ fuse_opt_add_arg(outargs, "-ho");
+ cmsfs_fuse_main(outargs, &cmsfs_oper);
+ exit(0);
+ case KEY_VERSION:
+ fprintf(stderr, COMP "FUSE file system for CMS disks "
+ "program version %s\n", RELEASE_STRING);
+ fprintf(stderr, "Copyright IBM Corp. 2010\n");
+ fuse_opt_add_arg(outargs, "--version");
+ exit(0);
+
+ default:
+ DIE("Process arguments error\n");
+ }
+}
+
+static void map_device(int fd)
+{
+ int prot;
+
+ /* fstat on block device says st_size = 0... */
+ lseek(fd, 0, SEEK_SET);
+ cmsfs.size = lseek(fd, 0, SEEK_END);
+ DEBUG("mmap size: %lu", cmsfs.size);
+
+ /* map the whole block device for speeding-up access */
+ if (cmsfs.readonly)
+ prot = PROT_READ;
+ else
+ prot = PROT_READ | PROT_WRITE;
+ cmsfs.map = mmap(NULL, cmsfs.size, prot, MAP_SHARED, fd, 0);
+ if (cmsfs.map == MAP_FAILED)
+ DIE_PERROR("mmap failed");
+ DEBUG(" addr: %p read-only: %d\n", cmsfs.map, cmsfs.readonly);
+}
+
+static void cmsfs_init(int fd)
+{
+ map_device(fd);
+
+ /* calculate blocksize dependent values */
+ cmsfs.data_block_mask = cmsfs.blksize - 1;
+ cmsfs.nr_blocks_512 = cmsfs.blksize / 512;
+
+ cmsfs.fixed_ptrs_per_block = cmsfs.blksize / sizeof(struct fixed_ptr);
+ cmsfs.var_ptrs_per_block = cmsfs.blksize / sizeof(struct var_ptr);
+
+ cmsfs.bits_per_data_block = get_order(cmsfs.blksize);
+
+ /* store directory information */
+ cmsfs.dir_levels = get_levels(cmsfs.fdir);
+ cmsfs.files = get_files_count(cmsfs.fdir);
+
+ /* alloc cache entries for all files */
+ cmsfs.fcache_max = max_cache_entries();
+ cmsfs.fcache = calloc(cmsfs.fcache_max, sizeof(struct fcache_entry));
+
+ cmsfs.amap = get_fop(cmsfs.fdir + sizeof(struct fst_entry));
+ cmsfs.amap_levels = get_levels(cmsfs.fdir + sizeof(struct fst_entry));
+ cmsfs.amap_bytes_per_block = cmsfs.blksize * 8 * cmsfs.blksize;
+
+ if (!hcreate_r(cmsfs.fcache_max, &cmsfs.htab))
+ DIE("hcreate failed\n");
+
+ list_init(&text_type_list);
+ scan_conf_file(&text_type_list);
+ list_init(&open_file_list);
+}
+
+int main(int argc, char *argv[])
+{
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ char *fsname;
+ int rc, fd;
+
+#ifdef DEBUG_ENABLED
+ logfile = fopen(DEBUG_LOGFILE, "w");
+ if (logfile == NULL)
+ DIE_PERROR("Cannot open file " DEBUG_LOGFILE " for writing");
+#endif
+
+ if (fuse_opt_parse(&args, &cmsfs, cmsfs_opts,
+ cmsfs_process_args) == -1)
+ DIE("Failed to parse option\n");
+
+ if (!cmsfs.device)
+ DIE("Missing device\n"
+ "Try '%s --help' for more information\n", argv[0]);
+
+ DEBUG("using device: %s", cmsfs.device);
+ fd = get_device_info(&cmsfs);
+ DEBUG(" blocksize: %d\n", cmsfs.blksize);
+
+ fsname = malloc(FSNAME_MAX_LEN);
+ if (fsname == NULL)
+ DIE_PERROR("malloc failed");
+
+#if FUSE_VERSION >= 27
+ snprintf(fsname, FSNAME_MAX_LEN, "-osubtype=cmsfs,fsname=%s",
+ cmsfs.device);
+#else
+ snprintf(fsname, FSNAME_MAX_LEN, "-ofsname=%s", cmsfs.device);
+#endif
+ fuse_opt_add_arg(&args, fsname);
+ free(fsname);
+
+ cmsfs_init(fd);
+ cmsfs.fd = fd;
+
+ if (cmsfs.readonly)
+ fuse_opt_add_arg(&args, "-oro");
+ /* force single threaded mode which requires no locking */
+ fuse_opt_add_arg(&args, "-s");
+ /* force immediate file removal */
+ fuse_opt_add_arg(&args, "-ohard_remove");
+
+ if (cmsfs.mode == BINARY_MODE &&
+ (cmsfs.codepage_from != NULL || cmsfs.codepage_to != NULL))
+ DIE("Incompatible options, select -a or -t if using --from or --to\n");
+
+ if (cmsfs.mode != BINARY_MODE) {
+ if (cmsfs.codepage_from == NULL)
+ cmsfs.codepage_from = CODEPAGE_EDF;
+ if (cmsfs.codepage_to == NULL)
+ cmsfs.codepage_to = CODEPAGE_LINUX;
+
+ setup_iconv(&cmsfs.iconv_from, cmsfs.codepage_from,
+ cmsfs.codepage_to);
+ setup_iconv(&cmsfs.iconv_to, cmsfs.codepage_to,
+ cmsfs.codepage_from);
+ }
+
+ rc = cmsfs_fuse_main(&args, &cmsfs_oper);
+
+ fuse_opt_free_args(&args);
+#ifdef DEBUG_ENABLED
+ fclose(logfile);
+#endif
+ hdestroy_r(&cmsfs.htab);
+ return rc;
+}
diff --git a/cmsfs-fuse/cmsfs-fuse.h b/cmsfs-fuse/cmsfs-fuse.h
new file mode 100644
index 0000000..6e7fda6
--- /dev/null
+++ b/cmsfs-fuse/cmsfs-fuse.h
@@ -0,0 +1,134 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * Data structures.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#ifndef _CMSFS_H
+#define _CMSFS_H
+
+#define _GNU_SOURCE
+#include <search.h>
+#include <iconv.h>
+#include "list.h"
+#include "list.h"
+
+#define COMP "cmsfs-fuse: "
+extern struct cmsfs cmsfs;
+
+/* conversion between absolute and relative addresses */
+#define ABS(x) ((x - 1) * cmsfs.blksize)
+#define REL(x) ((x / cmsfs.blksize) + 1)
+
+struct fcache_entry {
+ /* filename used as hash key */
+ char name[18];
+ /* location of fst entry */
+ off_t fst_addr;
+ /* filename string address */
+ char *str;
+};
+
+enum cmsfs_mode {
+ BINARY_MODE,
+ TEXT_MODE,
+ TYPE_MODE,
+};
+
+/* the per device global struture */
+struct cmsfs {
+ /* name of the block device, e.g. /dev/dasde */
+ const char *device;
+ /* global file descriptor of the underlying block device */
+ int fd;
+ /* start of mmap of the whole block device */
+ char *map;
+ /* size of the disk */
+ size_t size;
+ /* formatted blocksize */
+ int blksize;
+ /* number of 512 byte blocks per block */
+ int nr_blocks_512;
+ /* disk info */
+ unsigned int format;
+ /* device is read only */
+ int readonly;
+ /* access permission for other users */
+ int allow_other;
+ /* offset to file directory root FST */
+ off_t fdir;
+ /* offset to allocation map */
+ off_t amap;
+ /* depth of directories */
+ int dir_levels;
+ /* depth of allocation maps */
+ int amap_levels;
+ /* files count on the device */
+ int files;
+ /* conversion mode */
+ enum cmsfs_mode mode;
+ /* iconv codepage options */
+ const char *codepage_from;
+ const char *codepage_to;
+ iconv_t iconv_from;
+ iconv_t iconv_to;
+
+ /* disk stats */
+ int total_blocks;
+ int used_blocks;
+ /* blocks reserved for outstanding meta data */
+ int reserved_blocks;
+
+ /* constants */
+ int fixed_ptrs_per_block;
+ int var_ptrs_per_block;
+ int bits_per_data_block;
+ int bits_per_ptr_block;
+ int data_block_mask;
+ int amap_bytes_per_block;
+
+ /* file cache */
+ struct fcache_entry *fcache;
+ int fcache_used;
+ int fcache_max;
+ struct hsearch_data htab;
+};
+#define MAX_TYPE_LEN 9
+
+struct filetype {
+ char name[MAX_TYPE_LEN];
+ struct list list;
+};
+
+
+#define MAX_TYPE_LEN 9
+
+#define NULL_BLOCK 0
+#define VAR_FILE_END 1
+
+#define PTRS_PER_BLOCK (cmsfs.fixed_ptrs_per_block)
+#define VPTRS_PER_BLOCK (cmsfs.var_ptrs_per_block)
+#define DATA_BLOCK_MASK (cmsfs.data_block_mask)
+#define BITS_PER_DATA_BLOCK (cmsfs.bits_per_data_block)
+extern int scan_conf_file(struct list *list);
+extern int is_edf_char(int c);
+#define BYTES_PER_BLOCK (cmsfs.amap_bytes_per_block)
+
+extern int get_device_info(struct cmsfs *cmsfs);
+extern int scan_conf_file(struct list *list);
+extern int is_edf_char(int c);
+
+#ifndef _CMSFS_FSCK
+int _read(void *, size_t, off_t);
+int _write(const void *, size_t, off_t);
+int _zero(off_t, size_t);
+off_t get_fixed_pointer(off_t);
+
+off_t get_free_block(void);
+off_t get_zero_block(void);
+void free_block(off_t);
+#endif
+
+#endif
diff --git a/cmsfs-fuse/config.c b/cmsfs-fuse/config.c
new file mode 100644
index 0000000..9f9de45
--- /dev/null
+++ b/cmsfs-fuse/config.c
@@ -0,0 +1,122 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * Config option parsing.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include "cmsfs-fuse.h"
+#include "helper.h"
+#include "zt_common.h"
+
+#define MAX_LINE_LEN 80
+
+char *conffile;
+
+int open_conf_file(FILE **fh)
+{
+ const char *home_env = getenv("HOME");
+
+ if (home_env == NULL)
+ goto no_home;
+
+ conffile = malloc(4096);
+ if (conffile == NULL)
+ DIE_PERROR("malloc failed");
+ sprintf(conffile, "%s/.cmsfs-fuse/filetypes.conf", home_env);
+ *fh = fopen(conffile, "r");
+ if (*fh == NULL)
+ goto no_home;
+out:
+ DEBUG("using config file: %s\n", conffile);
+ return 0;
+
+no_home:
+ sprintf(conffile, "%s/%s", TOOLS_SYSCONFDIR,
+ "/cmsfs-fuse/filetypes.conf");
+ *fh = fopen(conffile, "r");
+ if (*fh == NULL) {
+ free(conffile);
+ return -ENOENT;
+ }
+ goto out;
+}
+
+void add_filetype(char *name, struct list *head)
+{
+ struct filetype *entry;
+
+ entry = malloc(sizeof(*entry));
+ if (entry == NULL)
+ DIE_PERROR("malloc failed");
+ strncpy(entry->name, name, MAX_TYPE_LEN);
+ list_add(&entry->list, head);
+}
+
+int filetype_valid(const char *type, int line)
+{
+ unsigned int i;
+
+ if (strlen(type) > 8) {
+ WARN("entry too long in line: %d in config file: %s\n",
+ line, conffile);
+ return 0;
+ }
+
+ for (i = 0; i < strlen(type); i++)
+ if (!is_edf_char(*(type + i))) {
+ WARN("invalid character in line: %d in config file: %s\n",
+ line, conffile);
+ return 0;
+ }
+
+ return 1;
+}
+
+int scan_conf_file(struct list *head)
+{
+ char buf[MAX_LINE_LEN], *tmp;
+ int line = 0;
+ FILE *fh;
+
+ if (open_conf_file(&fh) < 0)
+ return -ENOENT;
+
+ while (fgets(buf, MAX_LINE_LEN, fh) != NULL) {
+ line++;
+ tmp = buf;
+ while (isblank(*tmp))
+ tmp++;
+
+ if (*tmp == '\n')
+ continue;
+
+ /*
+ * Skip comments, comment must be "# " because # is a valid
+ * EDF character.
+ */
+ if (strlen(tmp) > 1 && *tmp == '#' && *(tmp + 1) == ' ')
+ continue;
+
+ /* remove trailing \n */
+ if (strlen(tmp) && *(tmp + strlen(tmp) - 1) == '\n')
+ *(tmp + strlen(tmp) - 1) = '\0';
+
+ if (filetype_valid(tmp, line))
+ add_filetype(tmp, head);
+ }
+ fclose(fh);
+ free(conffile);
+ return 0;
+}
diff --git a/cmsfs-fuse/dasd.c b/cmsfs-fuse/dasd.c
new file mode 100644
index 0000000..d79d34d
--- /dev/null
+++ b/cmsfs-fuse/dasd.c
@@ -0,0 +1,224 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * DASD specific functions.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include "helper.h"
+#include "edf.h"
+#include "cmsfs-fuse.h"
+
+typedef struct dasd_info {
+ unsigned int devno; /* S/390 devno */
+ unsigned int real_devno; /* for aliases */
+ unsigned int schid; /* S/390 subchannel identifier */
+ unsigned int cu_type :16; /* from SenseID */
+ unsigned int cu_model :8; /* from SenseID */
+ unsigned int dev_type :16; /* from SenseID */
+ unsigned int dev_model :8; /* from SenseID */
+ unsigned int open_count;
+ unsigned int req_queue_len;
+ unsigned int chanq_len; /* length of chanq */
+ char type[4]; /* from discipline.name, 'none' for unknown */
+ unsigned int status; /* current device level */
+ unsigned int label_block; /* where to find the VOLSER */
+ unsigned int FBA_layout; /* fixed block size (like AIXVOL) */
+ unsigned int characteristics_size;
+ unsigned int confdata_size;
+ char characteristics[64]; /* from read_device_characteristics */
+ char configuration_data[256]; /* from read_configuration_data */
+} dasd_info_t;
+
+typedef struct dasd_info2 {
+ unsigned int devno; /* S/390 devno */
+ unsigned int real_devno; /* for aliases */
+ unsigned int schid; /* S/390 subchannel identifier */
+ unsigned int cu_type :16; /* from SenseID */
+ unsigned int cu_model :8; /* from SenseID */
+ unsigned int dev_type :16; /* from SenseID */
+ unsigned int dev_model :8; /* from SenseID */
+ unsigned int open_count;
+ unsigned int req_queue_len;
+ unsigned int chanq_len; /* length of chanq */
+ char type[4]; /* from discipline.name, 'none' for unknown */
+ unsigned int status; /* current device level */
+ unsigned int label_block; /* where to find the VOLSER */
+ unsigned int FBA_layout; /* fixed block size (like AIXVOL) */
+ unsigned int characteristics_size;
+ unsigned int confdata_size;
+ char characteristics[64]; /* from read_device_characteristics */
+ char configuration_data[256]; /* from read_configuration_data */
+ unsigned int format; /* format info like formatted/cdl/ldl/... */
+ unsigned int features; /* dasd features like 'ro',... */
+ unsigned int reserved[8];
+} dasd_info2_t;
+
+#define HDIO_GETGEO 0x0301
+#define BLKSSZGET _IO(0x12, 104)
+#define BIODASDINFO _IOR('D', 1, dasd_info_t)
+#define BIODASDINFO2 _IOR('D', 3, dasd_info2_t)
+
+/* CMS disk label starts with ASCII string "CMS1" */
+#define VOL_LABEL_EBCDIC 0xc3d4e2f1
+
+#define DASD_FORMAT_LDL 1
+
+static int disk_supported(int fd, struct cmsfs *cmsfs)
+{
+ unsigned int cms_id = VOL_LABEL_EBCDIC;
+ struct cms_label label;
+ int offset, rc;
+
+ /* check that this is a ldl disk */
+ if (cmsfs->format != DASD_FORMAT_LDL) {
+ fprintf(stderr, COMP "Disk not LDL formatted\n");
+ return 0;
+ }
+
+ /* label is on block number 3 */
+ offset = 2 * cmsfs->blksize;
+
+ rc = lseek(fd, offset, SEEK_SET);
+ if (rc < 0) {
+ perror(COMP "lseek failed");
+ return 0;
+ }
+
+ rc = read(fd, &label, sizeof(label));
+ if (rc < 0) {
+ perror(COMP "read failed");
+ return 0;
+ }
+
+ /* check that the label contains the CMS1 string */
+ if (memcmp(label.id, &cms_id, sizeof(cms_id)) != 0) {
+ fprintf(stderr, COMP "Disk is not a CMS disk\n");
+ return 0;
+ }
+
+ DEBUG(" DOP: %d", label.dop);
+ /* block number 5 means 0x4000... */
+ cmsfs->fdir = (label.dop - 1) * cmsfs->blksize;
+ DEBUG(" fdir: %lx", cmsfs->fdir);
+ /* get disk usage for statfs */
+ cmsfs->total_blocks = label.total_blocks;
+ cmsfs->used_blocks = label.used_blocks;
+ DEBUG(" Total blocks: %d Used blocks: %d",
+ cmsfs->total_blocks, cmsfs->used_blocks);
+ return 1;
+}
+
+static void get_device_info_bdev(int fd, struct cmsfs *cmsfs)
+{
+ struct dasd_info2 *info = NULL;
+
+ if (ioctl(fd, BLKSSZGET, &cmsfs->blksize) != 0)
+ DIE("ioctl error get blocksize\n");
+
+ info = malloc(sizeof(struct dasd_info2));
+ if (info == NULL)
+ DIE_PERROR("malloc failed");
+
+ /* get disk information */
+ if (ioctl(fd, BIODASDINFO2, info) == 0) {
+ /* INFO2 failed - try INFO using the same (larger) buffer */
+ if (ioctl(fd, BIODASDINFO, info) != 0)
+ DIE("ioctl dasd info failed\n");
+ }
+ cmsfs->format = info->format;
+ free(info);
+}
+
+static int blocksizes[] = { 4096, 512, 2048, 1024 };
+
+static void get_device_info_file(int fd, struct cmsfs *cmsfs)
+{
+ unsigned int cms_id = VOL_LABEL_EBCDIC;
+ unsigned int i;
+ char label[4];
+ off_t offset;
+ int rc;
+
+ cmsfs->blksize = 0;
+
+ /*
+ * Read the blocksize from label. Unfortunately the blocksize
+ * position depends on the blocksize... time for some heuristics.
+ */
+ for (i = 0; i < ARRAY_SIZE(blocksizes); i++) {
+ offset = blocksizes[i] * 2;
+
+ rc = lseek(fd, offset, SEEK_SET);
+ if (rc < 0)
+ DIE_PERROR("lseek failed");
+
+ rc = read(fd, &label, 4);
+ if (rc < 0)
+ DIE_PERROR("read failed");
+
+ /* check if the label contains the CMS1 string */
+ if (memcmp(label, &cms_id, sizeof(cms_id)) == 0) {
+ cmsfs->blksize = blocksizes[i];
+ break;
+ }
+ }
+
+ if (!cmsfs->blksize)
+ DIE("Error detecting blocksize from file!\n");
+
+ /*
+ * Hardcoded since the label doesn't contain that info.
+ * Checking the disk identifier must be sufficient.
+ */
+ cmsfs->format = DASD_FORMAT_LDL;
+}
+
+int get_device_info(struct cmsfs *cmsfs)
+{
+ struct stat stat;
+ int fd;
+
+ /*
+ * Open writable, if write access is not granted fall back to
+ * read only.
+ */
+ fd = open(cmsfs->device, O_RDWR);
+ if (fd < 0) {
+ if (errno == EROFS) {
+ cmsfs->readonly = 1;
+ fd = open(cmsfs->device, O_RDONLY);
+ if (fd < 0)
+ DIE_PERROR("open failed");
+ } else
+ DIE_PERROR("open failed");
+ }
+
+ if (fstat(fd, &stat) < 0)
+ DIE_PERROR("fstat failed");
+
+ if (S_ISBLK(stat.st_mode))
+ get_device_info_bdev(fd, cmsfs);
+ else if (S_ISREG(stat.st_mode))
+ get_device_info_file(fd, cmsfs);
+ else
+ goto error;
+
+ if (!disk_supported(fd, cmsfs))
+ goto error;
+ return fd;
+
+error:
+ DIE("Unsupported disk\n");
+}
diff --git a/cmsfs-fuse/ebcdic.h b/cmsfs-fuse/ebcdic.h
new file mode 100644
index 0000000..9183f09
--- /dev/null
+++ b/cmsfs-fuse/ebcdic.h
@@ -0,0 +1,153 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * EBCDIC to ASCII conversion.
+ * EDF uses an EBCDIC codepage based on 037 with some modifications.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#ifndef _EBCDIC_H
+#define _EBCDIC_H
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+/*
+ * EBCDIC 037 -> ISO8859-1
+ * changes:
+ * 0x5f: 0xaa -> 0x5e ^
+ * 0xad: 0x07 -> 0x5b [
+ * 0xbd: 0x07 -> 0x5d ]
+ */
+
+static char ebc2asc[256] = {
+/* 0x00 */
+ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F,
+ 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+/* 0x10 */
+ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07,
+ 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+/* 0x20 */
+ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07,
+/* 0x30 */
+ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04,
+ 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A,
+/* 0x40 */
+ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86,
+ 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
+/* 0x50 */
+ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07,
+ 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E,
+/* 0x60 */
+ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F,
+ 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
+/* 0x70 */
+ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
+/* 0x80 */
+ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1,
+/* 0x90 */
+ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
+ 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07,
+/* 0xa0 */
+ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x5B, 0x07, 0x07,
+/* 0xb0 */
+ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC,
+ 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x5D, 0x07, 0x07,
+/* 0xc0 */
+ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07,
+/* 0xd0 */
+ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
+ 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98,
+/* 0xe0 */
+ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07,
+/* 0xf0 */
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07
+};
+
+/* ISO8859-1 -> EBCDIC 037 */
+static char asc2ebc[256] = {
+/* 0x00 */
+ 0x00, 0x01, 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F,
+ 0x16, 0x05, 0x15, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+/* 0x10 */
+ 0x10, 0x11, 0x12, 0x13, 0x3C, 0x3D, 0x32, 0x26,
+ 0x18, 0x19, 0x3F, 0x27, 0x22, 0x1D, 0x1E, 0x1F,
+/* 0x20 */
+ 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D,
+ 0x4D, 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61,
+/* 0x30 */
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+ 0xF8, 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, 0x6E, 0x6F,
+/* 0x40 */
+ 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+ 0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6,
+/* 0x50 */
+ 0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6,
+ 0xE7, 0xE8, 0xE9, 0xBA, 0xE0, 0xBB, 0xB0, 0x6D,
+/* 0x60 */
+ 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
+/* 0x70 */
+ 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
+ 0xA7, 0xA8, 0xA9, 0xC0, 0x4F, 0xD0, 0xA1, 0x07,
+/* 0x80 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0x90 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0xa0 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0xb0 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0xc0 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0xd0 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0xe0 */
+ 0x3F, 0x59, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+/* 0xf0 */
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x90, 0x3F, 0x3F, 0x3F, 0x3F, 0xEA, 0x3F, 0xFF
+};
+
+#define EBCDIC_ENCODE 0x0
+#define EBCDIC_DECODE 0x1
+
+static inline void a2e(char *dst, const char *src, int len, int to)
+{
+ char *conv;
+ int i;
+
+ if (to == EBCDIC_ENCODE)
+ conv = asc2ebc;
+ else
+ conv = ebc2asc;
+ for (i = 0; i < len; i++)
+ dst[i] = conv[(unsigned int)src[i]];
+}
+
+static inline void ebcdic_enc(char *dst, const char *src, int len)
+{
+ a2e(dst, src, len, EBCDIC_ENCODE);
+}
+
+static inline void ebcdic_dec(char *dst, const char *src, int len)
+{
+ a2e(dst, src, len, EBCDIC_DECODE);
+}
+
+#endif
diff --git a/cmsfs-fuse/edf.h b/cmsfs-fuse/edf.h
new file mode 100644
index 0000000..8c74a4e
--- /dev/null
+++ b/cmsfs-fuse/edf.h
@@ -0,0 +1,123 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * EDF and label structures.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#ifndef _EDF_H
+#define _EDF_H
+
+#include "helper.h"
+
+/*
+ * File status table entry
+ */
+struct fst_entry {
+ char name[8];
+ char type[8];
+ char res1[8];
+
+ short int mode;
+ char res2[4];
+
+ char record_format;
+ char flag;
+ int record_len;
+ char res3[4];
+
+ int fop;
+ /* number of data blocks (not incl. pointer blocks) */
+ int nr_blocks;
+ int nr_records;
+ char levels;
+ char ptr_size;
+ char date[6];
+ char res4[4];
+};
+
+struct cms_label {
+ char id[6];
+ char user_id[6];
+
+ unsigned int blocksize;
+ unsigned int dop;
+ unsigned int f_cylinders;
+ unsigned int max_cylinders;
+
+ unsigned int total_blocks;
+ unsigned int used_blocks;
+
+ unsigned int fst_entry_size;
+ unsigned int fst_per_block;
+
+ char date[6];
+ unsigned int res1[3];
+ char res2[8];
+};
+
+#define RECORD_LEN_VARIABLE 0xe5
+#define RECORD_LEN_FIXED 0xc6
+
+/* TODO: correct for fixed? */
+#define MAX_RECORD_LEN 0xffff
+
+#define FST_ENTRY_SIZE sizeof(struct fst_entry)
+#define FST_ENTRY_DIR_NAME 0x0000000100000000ULL
+#define FST_ENTRY_DIR_TYPE 0xc4c9d9c5c3e3d6d9ULL /* 'DIRECTOR' */
+#define FST_ENTRY_ALLOC_NAME 0x0000000200000000ULL
+#define FST_ENTRY_ALLOC_TYPE 0xc1d3d3d6c3d4c1d7ULL /* 'ALLOCMAP' */
+
+#define FST_FLAG_CENTURY 0x0008
+#define FST_FOP_OFFSET 0x28
+#define FST_LEVEL_OFFSET 0x34
+
+#define VAR_RECORD_HEADER_SIZE 0x2
+#define VAR_RECORD_SPANNED 0xffffffff
+
+#define PTR_SIZE (sizeof(struct fixed_ptr))
+#define VPTR_SIZE (sizeof(struct var_ptr))
+
+struct fixed_ptr {
+ unsigned int next;
+};
+
+struct var_ptr {
+ unsigned int next;
+ int hi_record_nr;
+ unsigned int disp;
+};
+
+static inline int is_directory(const char *name,
+ const char *type)
+{
+ if ((*(unsigned long long *) name) != FST_ENTRY_DIR_NAME)
+ return 0;
+ if ((*(unsigned long long *) type) != FST_ENTRY_DIR_TYPE)
+ return 0;
+ return 1;
+}
+
+static inline int is_allocmap(const char *name,
+ const char *type)
+{
+ if ((*(unsigned long long *) name) != FST_ENTRY_ALLOC_NAME)
+ return 0;
+ if ((*(unsigned long long *) type) != FST_ENTRY_ALLOC_TYPE)
+ return 0;
+ return 1;
+}
+
+static inline int is_file(unsigned long long *name, unsigned long long *type)
+{
+ if (*name == 0ULL)
+ return 0;
+
+ /* Assumption: type = 0 is not legal */
+ if (*type == 0ULL)
+ return 0;
+ return 1;
+}
+
+#endif
diff --git a/cmsfs-fuse/etc/filetypes.conf b/cmsfs-fuse/etc/filetypes.conf
new file mode 100644
index 0000000..6de94dc
--- /dev/null
+++ b/cmsfs-fuse/etc/filetypes.conf
@@ -0,0 +1,107 @@
+#
+# Filetypes that are interpreted as text files. If you want an EBCDIC
+# file translated to ASCII, add the extension here.
+#
+# Comments must include a space after the #
+
+# Add your extensions here:
+PRM
+CONF
+
+# The following types were taken from the z/VM TCPIP.DATA file:
+$EXEC
+$REXX
+$XEDIT
+AMS
+AMSERV
+ANN
+ANNOUNCE
+APP
+APPEND
+ASC
+ASCII
+ASM
+ASM3705
+ASSEMBLE
+AVL
+AVAIL
+A37
+BASDATA
+BASIC
+BKS
+BKSHELF
+C
+C++
+CAT
+CATALOG
+CNTRL
+COB
+COBOL
+COPY
+CPP
+DIRECT
+DLCS
+DOCUMENT
+ESERV
+EXC
+EXEC
+FFT
+FOR
+FORM
+FORTRAN
+FREEFORT
+GCS
+GROUP
+H
+HPP
+HTM
+HTML
+H++
+JOB
+LISTING
+LOG
+LST
+MAC
+MACLIB
+MACRO
+MAK
+MAKE
+ME
+MEMBER
+MEMO
+MODULE
+NAM
+NAMES
+NETLOG
+NONE
+NOT
+NOTE
+NOTEBOOK
+OFS
+OPT
+OPTIONS
+PACKAGE
+PASCAL
+PKG
+PLAS
+PLI
+PLIOPT
+PLS
+PVT
+REXX
+RPG
+SCR
+SCRIPT
+STY
+STYLE
+TEXT
+TEXTXXXX
+TXT
+TXTXXXX
+UPDATE
+UPDT
+VMT
+VSBASIC
+VSBDATA
+XED
+XEDIT
diff --git a/cmsfs-fuse/helper.h b/cmsfs-fuse/helper.h
new file mode 100644
index 0000000..714b8a0
--- /dev/null
+++ b/cmsfs-fuse/helper.h
@@ -0,0 +1,54 @@
+/*
+ * cmsfs-fuse - CMS EDF filesystem support for Linux
+ * Common helper functions.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
+ */
+
+#ifndef _HELPER_H
+#define _HELPER_H
+
+extern FILE *logfile;
+#define DEBUG_LOGFILE "/tmp/cmsfs-fuse.log"
+
+#ifdef DEBUG_ENABLED
+#define DEBUG(...) \
+ do { \
+ fprintf(logfile, __VA_ARGS__); \
+ fflush(logfile); \
+ } while (0)
+#else
+#define DEBUG(...)
+#endif
+
+#define DIE(...) \
+ do { \
+ fprintf(stderr, COMP __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define DIE_PERROR(...) \
+ do { \
+ perror(COMP __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define BUG(x) \
+ if (x) { \
+ fprintf(stderr, COMP " assert failed at " \
+ __FILE__ ":%d in %s()\n", __LINE__, __func__); \
+ exit(1); \
+ }
+
+#define WARN(...) \
+ do { \
+ fprintf(stderr, COMP "Warning, " __VA_ARGS__); \
+ } while (0)
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+#define max(x, y) ((x) > (y) ? (x) : (y))
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#endif
--
1.7.3.5