// SPDX-License-Identifier: GPL-2.0-only
/*
* namei.c
*
* PURPOSE
* Inode name handling routines for the OSTA-UDF(tm) filesystem.
*
* COPYRIGHT
* (C) 1998-2004 Ben Fennema
* (C) 1999-2000 Stelias Computing Inc
*
* HISTORY
*
* 12/12/98 blf Created. Split out the lookup code from dir.c
* 04/19/99 blf link, mknod, symlink support
*/
#include "udfdecl.h"
#include "udf_i.h"
#include "udf_sb.h"
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/crc-itu-t.h>
#include <linux/exportfs.h>
#include <linux/iversion.h>
static inline int udf_match(int len1, const unsigned char *name1, int len2,
const unsigned char *name2)
{
if (len1 != len2)
return 0;
return !memcmp(name1, name2, len1);
}
/**
* udf_fiiter_find_entry - find entry in given directory.
*
* @dir: directory inode to search in
* @child: qstr of the name
* @iter: iter to use for searching
*
* This function searches in the directory @dir for a file name @child. When
* found, @iter points to the position in the directory with given entry.
*
* Returns 0 on success, < 0 on error (including -ENOENT).
*/
static int udf_fiiter_find_entry(struct inode *dir, const struct qstr *child,
struct udf_fileident_iter *iter)
{
int flen;
unsigned char *fname = NULL;
struct super_block *sb = dir->i_sb;
int isdotdot = child->len == 2 &&
child->name[0] == '.' && child->name[1] == '.';
int ret;
fname = kmalloc(UDF_NAME_LEN, GFP_NOFS);
if (!fname)
return -ENOMEM;
for (ret = udf_fiiter_init(iter, dir, 0);
!ret && iter->pos < dir->i_size;
ret = udf_fiiter_advance(iter)) {
if (iter->fi.fileCharacteristics & FID_FILE_CHAR_DELETED) {
if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNDELETE))
continue;
}
if (iter->fi.fileCharacteristics & FID_FILE_CHAR_HIDDEN) {
if (!UDF_QUERY_FLAG(sb, UDF_FLAG_UNHIDE))
continue;
}
if ((iter->fi.fileCharacteristics & FID_FILE_CHAR_PARENT) &&
isdotdot)
goto out_ok;
if (!iter->fi.lengthFileIdent)
continue;
flen = udf_get_filename(sb, iter->name,
iter->fi.lengthFileIdent, fname, UDF_NAME_LEN);
if (flen < 0) {
ret = flen;
goto out_err;
}
if (udf_match(flen, fname, child->len, child->name))
goto out_ok;
}
if (!ret)
ret = -ENOENT;
out_err:
udf_fiiter_release(iter);
out_ok:
kfree(fname);
return ret;
}
static struct dentry *udf_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct inode *inode = NULL;
struct udf_fileident_iter iter;
int err;
if (dentry->d_name.len > UDF_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);
err = udf_fiiter_find_entry(dir, &dentry->d_name, &iter);
if (err < 0 && err != -ENOENT)
return ERR_PTR(err);
if (err == 0) {
struct kernel_lb_addr loc;
loc = lelb_to_cpu(iter.fi.icb.extLocation);
udf_fiiter_release(&iter);
inode = udf_iget(dir->i_sb, &loc);
if (IS_ERR(inode))
return ERR_CAST(inode);
}
return d_splice_alias(inode, dentry);
}
static int udf_expand_dir_adinicb(struct inode *inode, udf_pblk_t *block)
{
udf_pblk_t newblock;
struct buffer_head *dbh = NULL;
struct kernel_lb_addr eloc;
struct extent_position epos;
uint8_t alloctype;
struct udf_inode_info *iinfo = UDF_I(inode);
struct udf_fileident_iter iter;
uint8_t *impuse;
int ret;
if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
alloctype = ICBTAG_FLAG_AD_SHORT;
else
alloctype = ICBTAG_FLAG_AD_LONG;
if (!inode->i_size) {
iinfo->i_alloc_type = alloctype;
mark_inode_dirty(inode);
return 0;
}
/* alloc block, and copy data to it */
*block = udf_new_block(inode->i_sb, inode,
iinfo->i_location.partitionReferenceNum,
iinfo->i_location.logicalBlockNum, &ret);
if (!(*block))
return ret;
newblock = udf_get_pblock(inode->i_sb, *block,
iinfo->i_location.partitionReferenceNum,
0);
if (newblock == 0xffffffff)
return -EFSCORRUPTED;
dbh = sb_getblk(inode->i_sb, newblock);
if (!dbh)
return -ENOMEM;
lock_buffer(dbh);
memcpy(dbh->b_data, iinfo->i_data, inode->i_size);
memset(dbh->b_data + inode->i_size, 0,
inode->i_sb->s_blocksize - inode->i_size);
set_buffer_uptodate(dbh);
unlock_buffer(dbh);
/* Drop inline data, add block instead */
iinfo->i_alloc_type = alloctype;
memset(iinfo->i_data + iinfo->i_