Description: cmsfs-fuse: Use pread/pwrite if mmap'ing the whole disk fails
Symptom:     Trying to mount a disk fails with -ENOMEM.
Problem:     On mounting a disk cmsfs-fuse tries to mmap the whole disk.
             This can fail for very large disks or if the virtual memory
             is limited.
Solution:    Add a fall back to pread and pwrite in case the mmap system call
             fails.
Problem-ID:  79223
---
 cmsfs-fuse/cmsfs-fuse.c |  102 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 80 insertions(+), 22 deletions(-)

--- a/cmsfs-fuse/cmsfs-fuse.c
+++ b/cmsfs-fuse/cmsfs-fuse.c
@@ -171,6 +171,13 @@ struct file_operations {
 static struct file_operations fops_fixed;
 static struct file_operations fops_variable;
 
+struct io_operations {
+	int (*read) (void *buf, size_t size, off_t addr);
+	int (*write) (const void *buf, size_t size, off_t addr);
+};
+
+struct io_operations io_ops;
+
 struct write_state {
 	int		block_state;
 	/* number of free bytes in the current block */
@@ -310,28 +317,65 @@ static inline struct file *get_fobj(stru
 	return (struct file *) fi->fh;
 }
 
-int _read(void *buf, size_t size, off_t addr)
+static int access_ok(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.label || addr > cmsfs.size)
-		return -EIO;
+		DIE("crossing disk blocks, addr: %x  size: %d\n",
+			(int) addr, (int) size);
+
+	if ((unsigned long long) addr < (unsigned long long) cmsfs.label
+	    || addr > cmsfs.size)
+		return 0;
+	return 1;
+}
 
+int read_memory(void *buf, size_t size, off_t addr)
+{
 	memcpy(buf, cmsfs.map + addr, size);
 	return 0;
 }
 
-int _write(const void *buf, size_t size, off_t addr)
+int read_syscall(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 < cmsfs.label || addr > cmsfs.size)
+	int rc;
+
+	rc = pread(cmsfs.fd, buf, size, addr);
+	if (rc < 0)
+		perror("pread failed");
+	return rc;
+}
+
+int _read(void *buf, size_t size, off_t addr)
+{
+	if (!access_ok(size, addr))
 		return -EIO;
 
+	return io_ops.read(buf, size, addr);
+}
+
+int write_syscall(const void *buf, size_t size, off_t addr)
+{
+	char *zbuf;
+	int rc;
+
+	if (buf == NULL) {
+		zbuf = malloc(size);
+		if (zbuf == NULL)
+			return -ENOMEM;
+		memset(zbuf, 0, size);
+		rc = pwrite(cmsfs.fd, zbuf, size, addr);
+		free(zbuf);
+	} else
+		rc = pwrite(cmsfs.fd, buf, size, addr);
+
+	if (rc < 0)
+		perror("pwrite failed");
+	return rc;
+}
+
+int write_memory(const void *buf, size_t size, off_t addr)
+{
 	if (buf == NULL)
 		memset(cmsfs.map + addr, 0, size);
 	else
@@ -339,6 +383,14 @@ int _write(const void *buf, size_t size,
 	return 0;
 }
 
+int _write(const void *buf, size_t size, off_t addr)
+{
+	if (!access_ok(size, addr))
+		return -EIO;
+
+	return io_ops.write(buf, size, addr);
+}
+
 int _zero(off_t addr, size_t size)
 {
 	return _write(NULL, size, addr);
@@ -4459,29 +4511,36 @@ static int cmsfs_process_args(void *data
 	}
 }
 
-static void map_device(int fd)
+static void init_io(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);
+	DEBUG("read-only: %d", cmsfs.readonly);
+	cmsfs.fd = fd;
+	cmsfs.size = (off_t) cmsfs.total_blocks * cmsfs.blksize;
+	DEBUG(" mmap size: %lu", cmsfs.size);
 
-	/* map the whole block device for speeding-up access */
+	/* try to 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);
+	if (cmsfs.map == MAP_FAILED) {
+		DEBUG("\nmmap failed, using pread/write for disk I/O.\n");
+		io_ops.read = &read_syscall;
+		io_ops.write = &write_syscall;
+	} else {
+		DEBUG("  addr: %p\n", cmsfs.map);
+		io_ops.read = &read_memory;
+		io_ops.write = &write_memory;
+	}
 }
 
 static void cmsfs_init(int fd)
 {
-	map_device(fd);
+	init_io(fd);
 
 	/* calculate blocksize dependent values */
 	cmsfs.data_block_mask = cmsfs.blksize - 1;
@@ -4550,7 +4609,6 @@ int main(int argc, char *argv[])
 	free(fsname);
 
 	cmsfs_init(fd);
-	cmsfs.fd = fd;
 
 	if (cmsfs.readonly)
 		fuse_opt_add_arg(&args, "-oro");
