// SPDX-License-Identifier: GPL-2.0-only
/*
* directory . c
*
* PURPOSE
* Directory related functions
*
*/
#include "udfdecl.h"
#include "udf_i.h"
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/bio.h>
#include <linux/crc-itu-t.h>
#include <linux/iversion.h>
static int udf_verify_fi(struct udf_fileident_iter *iter)
{
unsigned int len;
if (iter->fi.descTag.tagIdent != cpu_to_le16(TAG_IDENT_FID)) {
udf_err(iter->dir->i_sb,
"directory (ino %lu) has entry at pos %llu with incorrect tag %x\n" ,
iter->dir->i_ino, (unsigned long long )iter->pos,
le16_to_cpu(iter->fi.descTag.tagIdent));
return -EFSCORRUPTED;
}
len = udf_dir_entry_len(&iter->fi);
if (le16_to_cpu(iter->fi.lengthOfImpUse) & 3 ) {
udf_err(iter->dir->i_sb,
"directory (ino %lu) has entry at pos %llu with unaligned length of impUse field\n" ,
iter->dir->i_ino, (unsigned long long )iter->pos);
return -EFSCORRUPTED;
}
/*
* This is in fact allowed by the spec due to long impUse field but
* we don ' t support it . If there is real media with this large impUse
* field , support can be added .
*/
if (len > 1 << iter->dir->i_blkbits) {
udf_err(iter->dir->i_sb,
"directory (ino %lu) has too big (%u) entry at pos %llu\n" ,
iter->dir->i_ino, len, (unsigned long long )iter->pos);
return -EFSCORRUPTED;
}
if (iter->pos + len > iter->dir->i_size) {
udf_err(iter->dir->i_sb,
"directory (ino %lu) has entry past directory size at pos %llu\n" ,
iter->dir->i_ino, (unsigned long long )iter->pos);
return -EFSCORRUPTED;
}
if (udf_dir_entry_len(&iter->fi) !=
sizeof (struct tag) + le16_to_cpu(iter->fi.descTag.descCRCLength)) {
udf_err(iter->dir->i_sb,
"directory (ino %lu) has entry where CRC length (%u) does not match entry length (%u)\n" ,
iter->dir->i_ino,
(unsigned )le16_to_cpu(iter->fi.descTag.descCRCLength),
(unsigned )(udf_dir_entry_len(&iter->fi) -
sizeof (struct tag)));
return -EFSCORRUPTED;
}
return 0 ;
}
static int udf_copy_fi(struct udf_fileident_iter *iter)
{
struct udf_inode_info *iinfo = UDF_I(iter->dir);
u32 blksize = 1 << iter->dir->i_blkbits;
u32 off, len, nameoff;
int err;
/* Skip copying when we are at EOF */
if (iter->pos >= iter->dir->i_size) {
iter->name = NULL;
return 0 ;
}
if (iter->dir->i_size < iter->pos + sizeof (struct fileIdentDesc)) {
udf_err(iter->dir->i_sb,
"directory (ino %lu) has entry straddling EOF\n" ,
iter->dir->i_ino);
return -EFSCORRUPTED;
}
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
memcpy(&iter->fi, iinfo->i_data + iinfo->i_lenEAttr + iter->pos,
sizeof (struct fileIdentDesc));
err = udf_verify_fi(iter);
if (err < 0 )
return err;
iter->name = iinfo->i_data + iinfo->i_lenEAttr + iter->pos +
sizeof (struct fileIdentDesc) +
le16_to_cpu(iter->fi.lengthOfImpUse);
return 0 ;
}
off = iter->pos & (blksize - 1 );
len = min_t(u32, sizeof (struct fileIdentDesc), blksize - off);
memcpy(&iter->fi, iter->bh[0 ]->b_data + off, len);
if (len < sizeof (struct fileIdentDesc))
memcpy((char *)(&iter->fi) + len, iter->bh[1 ]->b_data,
sizeof (struct fileIdentDesc) - len);
err = udf_verify_fi(iter);
if (err < 0 )
return err;
/* Handle directory entry name */
nameoff = off + sizeof (struct fileIdentDesc) +
le16_to_cpu(iter->fi.lengthOfImpUse);
if (off + udf_dir_entry_len(&iter->fi) <= blksize) {
iter->name = iter->bh[0 ]->b_data + nameoff;
} else if (nameoff >= blksize) {
iter->name = iter->bh[1 ]->b_data + (nameoff - blksize);
} else {
iter->name = iter->namebuf;
len = blksize - nameoff;
memcpy(iter->name, iter->bh[0 ]->b_data + nameoff, len);
memcpy(iter->name + len, iter->bh[1 ]->b_data,
iter->fi.lengthFileIdent - len);
}
return 0 ;
}
/* Readahead 8k once we are at 8k boundary */
static void udf_readahead_dir(struct udf_fileident_iter *iter)
{
unsigned int ralen = 16 >> (iter->dir->i_blkbits - 9 );
struct buffer_head *tmp, *bha[16 ];
int i, num;
udf_pblk_t blk;
if (iter->loffset & (ralen - 1 ))
return ;
if (iter->loffset + ralen > (iter->elen >> iter->dir->i_blkbits))
ralen = (iter->elen >> iter->dir->i_blkbits) - iter->loffset;
num = 0 ;
for (i = 0 ; i < ralen; i++) {
blk = udf_get_lb_pblock(iter->dir->i_sb, &iter->eloc,
iter->loffset + i);
tmp = sb_getblk(iter->dir->i_sb, blk);
if (tmp && !buffer_uptodate(tmp) && !buffer_locked(tmp))
bha[num++] = tmp;
else
brelse(tmp);
}
if (num) {
bh_readahead_batch(num, bha, REQ_RAHEAD);
for (i = 0 ; i < num; i++)
brelse(bha[i]);
}
}
static struct buffer_head *udf_fiiter_bread_blk(struct udf_fileident_iter *iter)
{
udf_pblk_t blk;
udf_readahead_dir(iter);
blk = udf_get_lb_pblock(iter->dir->i_sb, &iter->eloc, iter->loffset);
return sb_bread(iter->dir->i_sb, blk);
}
/*
* Updates loffset to point to next directory block ; eloc , elen & epos are
* updated if we need to traverse to the next extent as well .
*/
static int udf_fiiter_advance_blk(struct udf_fileident_iter *iter)
{
int8_t etype = -1 ;
int err = 0 ;
iter->loffset++;
if (iter->loffset < DIV_ROUND_UP(iter->elen, 1 <<iter->dir->i_blkbits))
return 0 ;
iter->loffset = 0 ;
err = udf_next_aext(iter->dir, &iter->epos, &iter->eloc,
&iter->elen, &etype, 1 );
if (err < 0 )
return err;
else if (err == 0 || etype != (EXT_RECORDED_ALLOCATED >> 30 )) {
if (iter->pos == iter->dir->i_size) {
iter->elen = 0 ;
return 0 ;
}
udf_err(iter->dir->i_sb,
"extent after position %llu not allocated in directory (ino %lu)\n" ,
(unsigned long long )iter->pos, iter->dir->i_ino);
return -EFSCORRUPTED;
}
return 0 ;
}
static int udf_fiiter_load_bhs(struct udf_fileident_iter *iter)
{
int blksize = 1 << iter->dir->i_blkbits;
int off = iter->pos & (blksize - 1 );
int err;
struct fileIdentDesc *fi;
/* Is there any further extent we can map from? */
if (!iter->bh[0 ] && iter->elen) {
iter->bh[0 ] = udf_fiiter_bread_blk(iter);
if (!iter->bh[0 ]) {
err = -ENOMEM;
goto out_brelse;
}
if (!buffer_uptodate(iter->bh[0 ])) {
err = -EIO;
goto out_brelse;
}
}
/* There's no next block so we are done */
if (iter->pos >= iter->dir->i_size)
return 0 ;
/* Need to fetch next block as well? */
if (off + sizeof (struct fileIdentDesc) > blksize)
goto fetch_next;
fi = (struct fileIdentDesc *)(iter->bh[0 ]->b_data + off);
/* Need to fetch next block to get name? */
if (off + udf_dir_entry_len(fi) > blksize) {
fetch_next:
err = udf_fiiter_advance_blk(iter);
if (err)
goto out_brelse;
iter->bh[1 ] = udf_fiiter_bread_blk(iter);
if (!iter->bh[1 ]) {
err = -ENOMEM;
goto out_brelse;
}
if (!buffer_uptodate(iter->bh[1 ])) {
err = -EIO;
goto out_brelse;
}
}
return 0 ;
out_brelse:
brelse(iter->bh[0 ]);
brelse(iter->bh[1 ]);
iter->bh[0 ] = iter->bh[1 ] = NULL;
return err;
}
int udf_fiiter_init(struct udf_fileident_iter *iter, struct inode *dir,
loff_t pos)
{
struct udf_inode_info *iinfo = UDF_I(dir);
int err = 0 ;
int8_t etype;
iter->dir = dir;
iter->bh[0 ] = iter->bh[1 ] = NULL;
iter->pos = pos;
iter->elen = 0 ;
iter->epos.bh = NULL;
iter->name = NULL;
/*
* When directory is verified , we don ' t expect directory iteration to
* fail and it can be difficult to undo without corrupting filesystem .
* So just do not allow memory allocation failures here .
*/
iter->namebuf = kmalloc(UDF_NAME_LEN_CS0, GFP_KERNEL | __GFP_NOFAIL);
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
err = udf_copy_fi(iter);
goto out;
}
err = inode_bmap(dir, iter->pos >> dir->i_blkbits, &iter->epos,
&iter->eloc, &iter->elen, &iter->loffset, &etype);
if (err <= 0 || etype != (EXT_RECORDED_ALLOCATED >> 30 )) {
if (pos == dir->i_size)
return 0 ;
udf_err(dir->i_sb,
"position %llu not allocated in directory (ino %lu)\n" ,
(unsigned long long )pos, dir->i_ino);
err = -EFSCORRUPTED;
goto out;
}
err = udf_fiiter_load_bhs(iter);
if (err < 0 )
goto out;
err = udf_copy_fi(iter);
out:
if (err < 0 )
udf_fiiter_release(iter);
return err;
}
int udf_fiiter_advance(struct udf_fileident_iter *iter)
{
unsigned int oldoff, len;
int blksize = 1 << iter->dir->i_blkbits;
int err;
oldoff = iter->pos & (blksize - 1 );
len = udf_dir_entry_len(&iter->fi);
iter->pos += len;
if (UDF_I(iter->dir)->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
if (oldoff + len >= blksize) {
brelse(iter->bh[0 ]);
iter->bh[0 ] = NULL;
/* Next block already loaded? */
if (iter->bh[1 ]) {
iter->bh[0 ] = iter->bh[1 ];
iter->bh[1 ] = NULL;
} else {
err = udf_fiiter_advance_blk(iter);
if (err < 0 )
return err;
}
}
err = udf_fiiter_load_bhs(iter);
if (err < 0 )
return err;
}
return udf_copy_fi(iter);
}
void udf_fiiter_release(struct udf_fileident_iter *iter)
{
iter->dir = NULL;
brelse(iter->bh[0 ]);
brelse(iter->bh[1 ]);
iter->bh[0 ] = iter->bh[1 ] = NULL;
kfree(iter->namebuf);
iter->namebuf = NULL;
}
static void udf_copy_to_bufs(void *buf1, int len1, void *buf2, int len2,
int off, void *src, int len)
{
int copy;
if (off >= len1) {
off -= len1;
} else {
copy = min(off + len, len1) - off;
memcpy(buf1 + off, src, copy);
src += copy;
len -= copy;
off = 0 ;
}
if (len > 0 ) {
if (WARN_ON_ONCE(off + len > len2 || !buf2))
return ;
memcpy(buf2 + off, src, len);
}
}
static uint16_t udf_crc_fi_bufs(void *buf1, int len1, void *buf2, int len2,
int off, int len)
{
int copy;
uint16_t crc = 0 ;
if (off >= len1) {
off -= len1;
} else {
copy = min(off + len, len1) - off;
crc = crc_itu_t(crc, buf1 + off, copy);
len -= copy;
off = 0 ;
}
if (len > 0 ) {
if (WARN_ON_ONCE(off + len > len2 || !buf2))
return 0 ;
crc = crc_itu_t(crc, buf2 + off, len);
}
return crc;
}
static void udf_copy_fi_to_bufs(char *buf1, int len1, char *buf2, int len2,
int off, struct fileIdentDesc *fi,
uint8_t *impuse, uint8_t *name)
{
uint16_t crc;
int fioff = off;
int crcoff = off + sizeof (struct tag);
unsigned int crclen = udf_dir_entry_len(fi) - sizeof (struct tag);
char zeros[UDF_NAME_PAD] = {};
int endoff = off + udf_dir_entry_len(fi);
udf_copy_to_bufs(buf1, len1, buf2, len2, off, fi,
sizeof (struct fileIdentDesc));
off += sizeof (struct fileIdentDesc);
if (impuse)
udf_copy_to_bufs(buf1, len1, buf2, len2, off, impuse,
le16_to_cpu(fi->lengthOfImpUse));
off += le16_to_cpu(fi->lengthOfImpUse);
if (name) {
udf_copy_to_bufs(buf1, len1, buf2, len2, off, name,
fi->lengthFileIdent);
off += fi->lengthFileIdent;
udf_copy_to_bufs(buf1, len1, buf2, len2, off, zeros,
endoff - off);
}
crc = udf_crc_fi_bufs(buf1, len1, buf2, len2, crcoff, crclen);
fi->descTag.descCRC = cpu_to_le16(crc);
fi->descTag.descCRCLength = cpu_to_le16(crclen);
fi->descTag.tagChecksum = udf_tag_checksum(&fi->descTag);
udf_copy_to_bufs(buf1, len1, buf2, len2, fioff, fi, sizeof (struct tag));
}
void udf_fiiter_write_fi(struct udf_fileident_iter *iter, uint8_t *impuse)
{
struct udf_inode_info *iinfo = UDF_I(iter->dir);
void *buf1, *buf2 = NULL;
int len1, len2 = 0 , off;
int blksize = 1 << iter->dir->i_blkbits;
off = iter->pos & (blksize - 1 );
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
buf1 = iinfo->i_data + iinfo->i_lenEAttr;
len1 = iter->dir->i_size;
} else {
buf1 = iter->bh[0 ]->b_data;
len1 = blksize;
if (iter->bh[1 ]) {
buf2 = iter->bh[1 ]->b_data;
len2 = blksize;
}
}
udf_copy_fi_to_bufs(buf1, len1, buf2, len2, off, &iter->fi, impuse,
iter->name == iter->namebuf ? iter->name : NULL);
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) {
mark_inode_dirty(iter->dir);
} else {
mark_buffer_dirty_inode(iter->bh[0 ], iter->dir);
if (iter->bh[1 ])
mark_buffer_dirty_inode(iter->bh[1 ], iter->dir);
}
inode_inc_iversion(iter->dir);
}
void udf_fiiter_update_elen(struct udf_fileident_iter *iter, uint32_t new_elen)
{
struct udf_inode_info *iinfo = UDF_I(iter->dir);
int diff = new_elen - iter->elen;
/* Skip update when we already went past the last extent */
if (!iter->elen)
return ;
iter->elen = new_elen;
if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT)
iter->epos.offset -= sizeof (struct short_ad);
else if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_LONG)
iter->epos.offset -= sizeof (struct long_ad);
udf_write_aext(iter->dir, &iter->epos, &iter->eloc, iter->elen, 1 );
iinfo->i_lenExtents += diff;
mark_inode_dirty(iter->dir);
}
/* Append new block to directory. @iter is expected to point at EOF */
int udf_fiiter_append_blk(struct udf_fileident_iter *iter)
{
struct udf_inode_info *iinfo = UDF_I(iter->dir);
int blksize = 1 << iter->dir->i_blkbits;
struct buffer_head *bh;
sector_t block;
uint32_t old_elen = iter->elen;
int err;
int8_t etype;
if (WARN_ON_ONCE(iinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB))
return -EINVAL;
/* Round up last extent in the file */
udf_fiiter_update_elen(iter, ALIGN(iter->elen, blksize));
/* Allocate new block and refresh mapping information */
block = iinfo->i_lenExtents >> iter->dir->i_blkbits;
bh = udf_bread(iter->dir, block, 1 , &err);
if (!bh) {
udf_fiiter_update_elen(iter, old_elen);
return err;
}
err = inode_bmap(iter->dir, block, &iter->epos, &iter->eloc, &iter->elen,
&iter->loffset, &etype);
if (err <= 0 || etype != (EXT_RECORDED_ALLOCATED >> 30 )) {
udf_err(iter->dir->i_sb,
"block %llu not allocated in directory (ino %lu)\n" ,
(unsigned long long )block, iter->dir->i_ino);
return -EFSCORRUPTED;
}
if (!(iter->pos & (blksize - 1 ))) {
brelse(iter->bh[0 ]);
iter->bh[0 ] = bh;
} else {
iter->bh[1 ] = bh;
}
return 0 ;
}
struct short_ad *udf_get_fileshortad(uint8_t *ptr, int maxoffset, uint32_t *offset,
int inc)
{
struct short_ad *sa;
if ((!ptr) || (!offset)) {
pr_err("%s: invalidparms\n" , __func__);
return NULL;
}
if ((*offset + sizeof (struct short_ad)) > maxoffset)
return NULL;
else {
sa = (struct short_ad *)ptr;
if (sa->extLength == 0 )
return NULL;
}
if (inc)
*offset += sizeof (struct short_ad);
return sa;
}
struct long_ad *udf_get_filelongad(uint8_t *ptr, int maxoffset, uint32_t *offset, int inc)
{
struct long_ad *la;
if ((!ptr) || (!offset)) {
pr_err("%s: invalidparms\n" , __func__);
return NULL;
}
if ((*offset + sizeof (struct long_ad)) > maxoffset)
return NULL;
else {
la = (struct long_ad *)ptr;
if (la->extLength == 0 )
return NULL;
}
if (inc)
*offset += sizeof (struct long_ad);
return la;
}
Messung V0.5 in Prozent C=97 H=93 G=94
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland