Description: cmsfs-fuse: Add support for FBA-512 disks 
Symptom:     FBA-512 disks can not be mounted with cmsfs-fuse.
Problem:     For FBA-512 disks the label information is at a different
             position. Also the formatted block size may differ from the
             disk block size.
Solution:    Extend the label detection to work with FBA-512 disks and
             read the formatted block size always from the label.
Problem-ID:  75986 
---
 cmsfs-fuse/cmsfs-fuse.c |    9 +----
 cmsfs-fuse/cmsfs-fuse.h |    4 +-
 cmsfs-fuse/dasd.c       |   84 ++++++++++++++++++++++++++++++------------------
 3 files changed, 58 insertions(+), 39 deletions(-)

--- a/cmsfs-fuse/cmsfs-fuse.c
+++ b/cmsfs-fuse/cmsfs-fuse.c
@@ -92,8 +92,6 @@ static void usage(const char *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
@@ -318,7 +316,7 @@ int _read(void *buf, size_t size, off_t
 	    (addr & ~DATA_BLOCK_MASK))
 		DIE("read: crossing blocks addr: %lx  size: %ld\n",
 			addr, size);
-	if (addr < cmsfs.fdir || addr > cmsfs.size)
+	if (addr < cmsfs.label || addr > cmsfs.size)
 		return -EIO;
 
 	memcpy(buf, cmsfs.map + addr, size);
@@ -331,8 +329,7 @@ int _write(const void *buf, size_t size,
 	    (addr & ~DATA_BLOCK_MASK))
 		DIE("write: crossing blocks addr: %x  size: %d\n",
 			(int)addr, (int)size);
-
-	if ((addr < (2 * cmsfs.blksize)) || (addr > cmsfs.size))
+	if (addr < cmsfs.label || addr > cmsfs.size)
 		return -EIO;
 
 	if (buf == NULL)
@@ -2024,7 +2021,7 @@ static void update_block_count(void)
 {
 	int rc;
 
-	rc = _write(&cmsfs.used_blocks, sizeof(unsigned int), USED_BLOCK_ADDR);
+	rc = _write(&cmsfs.used_blocks, sizeof(unsigned int), cmsfs.label + 32);
 	BUG(rc < 0);
 }
 
--- a/cmsfs-fuse/cmsfs-fuse.h
+++ b/cmsfs-fuse/cmsfs-fuse.h
@@ -50,12 +50,12 @@ struct cmsfs {
 	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 label */
+	off_t		label;
 	/* offset to file directory root FST */
 	off_t		fdir;
 	/* offset to allocation map  */
--- a/cmsfs-fuse/dasd.c
+++ b/cmsfs-fuse/dasd.c
@@ -79,18 +79,9 @@ static int disk_supported(int fd, struct
 {
 	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;
+	int rc;
 
-	rc = lseek(fd, offset, SEEK_SET);
+	rc = lseek(fd, cmsfs->label, SEEK_SET);
 	if (rc < 0) {
 		perror(COMP "lseek failed");
 		return 0;
@@ -103,11 +94,35 @@ static int disk_supported(int fd, struct
 	}
 
 	/* 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");
+	if (memcmp(label.id, &cms_id, sizeof(cms_id)) != 0)
+		return 0;
+
+	/* label sanity checks */
+	if (label.blocksize != 4096 &&
+	    label.blocksize != 2048 &&
+	    label.blocksize != 1024 &&
+	    label.blocksize != 512) {
+		fprintf(stderr, COMP "Invalid disk block size!\n");
+		return 0;
+	}
+
+	if (label.dop != 4 && label.dop != 5) {
+		fprintf(stderr, COMP "Invalid disk origin pointer!\n");
 		return 0;
 	}
 
+	if (label.fst_entry_size != sizeof(struct fst_entry)) {
+		fprintf(stderr, COMP "Invalid FST entry size!\n");
+		return 0;
+	}
+
+	if (label.fst_per_block != label.blocksize / label.fst_entry_size) {
+		fprintf(stderr, COMP "Invalid FST per block value!\n");
+		return 0;
+	}
+
+	/* set the blocksize to the formatted one */
+	cmsfs->blksize = label.blocksize;
 	DEBUG("  DOP: %d", label.dop);
 	/* block number 5 means 0x4000... */
 	cmsfs->fdir = (label.dop - 1) * cmsfs->blksize;
@@ -117,10 +132,11 @@ static int disk_supported(int fd, struct
 	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)
+static void get_device_info_ioctl(int fd, struct cmsfs *cmsfs)
 {
 	struct dasd_info2 *info = NULL;
 
@@ -137,11 +153,15 @@ static void get_device_info_bdev(int fd,
 		if (ioctl(fd, BIODASDINFO, info) != 0)
 			DIE("ioctl dasd info failed\n");
 	}
-	cmsfs->format = info->format;
+
+	/* check that this is a ldl disk */
+	if (info->format != DASD_FORMAT_LDL)
+		DIE("Disk not LDL formatted\n");
+
 	free(info);
 }
 
-static int blocksizes[] = { 4096, 512, 2048, 1024 };
+static int label_offsets[] = { 4096, 512, 2048, 1024, 8192 };
 
 static void get_device_info_file(int fd, struct cmsfs *cmsfs)
 {
@@ -151,14 +171,14 @@ static void get_device_info_file(int fd,
 	off_t offset;
 	int rc;
 
-	cmsfs->blksize = 0;
+	cmsfs->label = 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;
+	for (i = 0; i < ARRAY_SIZE(label_offsets); i++) {
+		offset = label_offsets[i];
 
 		rc = lseek(fd, offset, SEEK_SET);
 		if (rc < 0)
@@ -170,19 +190,13 @@ static void get_device_info_file(int fd,
 
 		/* check if the label contains the CMS1 string */
 		if (memcmp(label, &cms_id, sizeof(cms_id)) == 0) {
-			cmsfs->blksize = blocksizes[i];
+			cmsfs->label = offset;
 			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;
+	if (!cmsfs->label)
+		DIE("Error CMS1 label not found!\n");
 }
 
 int get_device_info(struct cmsfs *cmsfs)
@@ -208,9 +222,17 @@ int get_device_info(struct cmsfs *cmsfs)
 	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))
+	if (S_ISBLK(stat.st_mode)) {
+		get_device_info_ioctl(fd, cmsfs);
+		cmsfs->label = 2 * cmsfs->blksize;
+
+		/* FBA disks have a different label location */
+		if (!disk_supported(fd, cmsfs)) {
+			cmsfs->label = cmsfs->blksize;
+			if (!disk_supported(fd, cmsfs))
+				goto error;
+		}
+	} else if (S_ISREG(stat.st_mode))
 		get_device_info_file(fd, cmsfs);
 	else
 		goto error;
