6057 lines
141 KiB
Diff
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
|
|
|