diff --git a/sys/fs/ext2fs/ext2_inode.c b/sys/fs/ext2fs/ext2_inode.c --- a/sys/fs/ext2fs/ext2_inode.c +++ b/sys/fs/ext2fs/ext2_inode.c @@ -605,8 +605,7 @@ if (ip->i_nlink <= 0) { ext2_extattr_free(ip); error = ext2_truncate(vp, (off_t)0, 0, NOCRED, td); - if (!(ip->i_flag & IN_E4EXTENTS)) - ip->i_rdev = 0; + ip->i_rdev = 0; mode = ip->i_mode; ip->i_mode = 0; ip->i_flag |= IN_CHANGE | IN_UPDATE; diff --git a/sys/fs/ext2fs/ext2_inode_cnv.c b/sys/fs/ext2fs/ext2_inode_cnv.c --- a/sys/fs/ext2fs/ext2_inode_cnv.c +++ b/sys/fs/ext2fs/ext2_inode_cnv.c @@ -96,6 +96,42 @@ #define XTIME_TO_NSEC(x) ((le32toh(x) & EXT3_NSEC_MASK) >> 2) +static inline bool +ext2_old_valid_dev(dev_t dev) +{ + return (major(dev) < 256 && minor(dev) < 256); +} + +static inline uint16_t +ext2_old_encode_dev(dev_t dev) +{ + return ((major(dev) << 8) | minor(dev)); +} + +static inline dev_t +ext2_old_decode_dev(uint16_t val) +{ + return (makedev((val >> 8) & 255, val & 255)); +} + +static inline uint32_t +ext2_new_encode_dev(dev_t dev) +{ + unsigned maj = major(dev); + unsigned min = minor(dev); + + return ((min & 0xff) | (maj << 8) | ((min & ~0xff) << 12)); +} + +static inline dev_t +ext2_new_decode_dev(uint32_t dev) +{ + unsigned maj = (dev & 0xfff00) >> 8; + unsigned min = (dev & 0xff) | ((dev >> 12) & 0xfff00); + + return (makedev(maj, min)); +} + /* * raw ext2 inode LE to host inode conversion */ @@ -172,7 +208,12 @@ ip->i_uid |= (uint32_t)le16toh(ei->e2di_uid_high) << 16; ip->i_gid |= (uint32_t)le16toh(ei->e2di_gid_high) << 16; - if ((ip->i_flag & IN_E4EXTENTS)) { + if (S_ISCHR(ip->i_mode) || S_ISBLK(ip->i_mode)) { + if (ei->e2di_blocks[0]) + ip->i_rdev = ext2_old_decode_dev(le32toh(ei->e2di_blocks[0])); + else + ip->i_rdev = ext2_new_decode_dev(le32toh(ei->e2di_blocks[1])); + } else if ((ip->i_flag & IN_E4EXTENTS)) { memcpy(ip->i_data, ei->e2di_blocks, sizeof(ei->e2di_blocks)); } else { for (i = 0; i < EXT2_NDADDR; i++) @@ -247,7 +288,16 @@ ei->e2di_gid = htole16(ip->i_gid & 0xffff); ei->e2di_gid_high = htole16(ip->i_gid >> 16 & 0xffff); - if ((ip->i_flag & IN_E4EXTENTS)) { + if (S_ISCHR(ip->i_mode) || S_ISBLK(ip->i_mode)) { + if (ext2_old_valid_dev(ip->i_rdev)) { + ei->e2di_blocks[0] = htole32(ext2_old_encode_dev(ip->i_rdev)); + ei->e2di_blocks[1] = 0; + } else { + ei->e2di_blocks[0] = 0; + ei->e2di_blocks[1] = htole32(ext2_new_encode_dev(ip->i_rdev)); + ei->e2di_blocks[2] = 0; + } + } else if ((ip->i_flag & IN_E4EXTENTS)) { memcpy(ei->e2di_blocks, ip->i_data, sizeof(ei->e2di_blocks)); } else { for (i = 0; i < EXT2_NDADDR; i++) diff --git a/sys/fs/ext2fs/ext2_vnops.c b/sys/fs/ext2fs/ext2_vnops.c --- a/sys/fs/ext2fs/ext2_vnops.c +++ b/sys/fs/ext2fs/ext2_vnops.c @@ -322,9 +322,6 @@ accmode_t accmode = ap->a_accmode; int error; - if (vp->v_type == VBLK || vp->v_type == VCHR) - return (EOPNOTSUPP); - /* * Disallow write attempts on read-only file systems; * unless the file is a socket, fifo, or a block or @@ -620,6 +617,18 @@ return (ext2_update(ap->a_vp, ap->a_waitfor == MNT_WAIT)); } +static int +ext2_check_mknod_limits(dev_t dev) +{ + unsigned maj = major(dev); + unsigned min = minor(dev); + + if (maj > EXT2_MAJOR_MAX || min > EXT2_MINOR_MAX) + return (EINVAL); + + return (0); +} + /* * Mknod vnode call */ @@ -633,20 +642,21 @@ ino_t ino; int error; + if (vap->va_rdev != VNOVAL) { + error = ext2_check_mknod_limits(vap->va_rdev); + if (error) + return (error); + } + error = ext2_makeinode(MAKEIMODE(vap->va_type, vap->va_mode), ap->a_dvp, vpp, ap->a_cnp); if (error) return (error); ip = VTOI(*vpp); ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; - if (vap->va_rdev != VNOVAL) { - /* - * Want to be able to use this to make badblock - * inodes, so don't truncate the dev number. - */ - if (!(ip->i_flag & IN_E4EXTENTS)) - ip->i_rdev = vap->va_rdev; - } + if (vap->va_rdev != VNOVAL) + ip->i_rdev = vap->va_rdev; + /* * Remove inode, then reload it through VFS_VGET so it is * checked to see if it is an alias of an existing entry in diff --git a/sys/fs/ext2fs/ext2fs.h b/sys/fs/ext2fs/ext2fs.h --- a/sys/fs/ext2fs/ext2fs.h +++ b/sys/fs/ext2fs/ext2fs.h @@ -429,4 +429,11 @@ #define EXT2_FIRST_INO(s) (le32toh((EXT2_SB(s)->e2fs->e2fs_rev) == \ E2FS_REV0) ? EXT2_FIRSTINO : le32toh(EXT2_SB(s)->e2fs->e2fs_first_ino)) +/* + * Linux major/minor values limits + */ +#define EXT2_MINORBITS (20) +#define EXT2_MAJOR_MAX (0xffffffff >> EXT2_MINORBITS) +#define EXT2_MINOR_MAX ((1 << EXT2_MINORBITS) - 1) + #endif /* !_FS_EXT2FS_EXT2FS_H_ */ diff --git a/sys/fs/ext2fs/inode.h b/sys/fs/ext2fs/inode.h --- a/sys/fs/ext2fs/inode.h +++ b/sys/fs/ext2fs/inode.h @@ -110,6 +110,7 @@ uint32_t i_gen; /* Generation number. */ uint64_t i_facl; /* EA block number. */ uint32_t i_flags; /* Status flags (chflags). */ + dev_t i_rdev; /* Major/minor inode values. */ union { struct { uint32_t i_db[EXT2_NDADDR]; /* Direct disk blocks. */ @@ -131,7 +132,6 @@ * di_db area. */ #define i_shortlink i_db -#define i_rdev i_db[0] /* File permissions. */ #define IEXEC 0000100 /* Executable. */