Goto sanos source index
//
// dir.c
//
// Disk filesystem directory routines
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
#define NAME_ALIGN_LEN(l) (((l) + 3) & ~3)
int find_dir_entry(struct inode *dir, char *name, int len, ino_t *retval) {
unsigned int block;
blkno_t blk;
struct buf *buf;
char *p;
struct dentry *de;
ino_t ino;
if (len <= 0) return -EINVAL;
if (len >= MAXPATH) return -ENAMETOOLONG;
if (!S_ISDIR(dir->desc->mode)) return -ENOTDIR;
if (checki(dir, S_IEXEC) < 0) return -EACCES;
for (block = 0; block < dir->desc->blocks; block++) {
blk = get_inode_block(dir, block);
if (blk == NOBLOCK) return -EIO;
buf = get_buffer(dir->fs->cache, blk);
if (!buf) return -EIO;
p = buf->data;
while (p < buf->data + dir->fs->blocksize) {
de = (struct dentry *) p;
if (fnmatch(name, len, de->name, de->namelen)) {
ino = de->ino;
release_buffer(dir->fs->cache, buf);
if (retval) *retval = ino;
return 0;
}
p += de->reclen;
}
release_buffer(dir->fs->cache, buf);
}
return -ENOENT;
}
int add_dir_entry(struct inode *dir, char *name, int len, ino_t ino) {
unsigned int block;
blkno_t blk;
struct buf *buf;
char *p;
struct dentry *de;
struct dentry *newde;
unsigned int minlen;
unsigned int newlen;
if (len <= 0) return -EINVAL;
if (len >= MAXPATH) return -ENAMETOOLONG;
if (!S_ISDIR(dir->desc->mode)) return -ENOTDIR;
if (checki(dir, S_IWRITE) < 0) return -EACCES;
newlen = sizeof(struct dentry) + NAME_ALIGN_LEN(len);
for (block = 0; block < dir->desc->blocks; block++) {
blk = get_inode_block(dir, block);
if (blk == NOBLOCK) return -EIO;
buf = get_buffer(dir->fs->cache, blk);
if (!buf) return -EIO;
p = buf->data;
while (p < buf->data + dir->fs->blocksize) {
de = (struct dentry *) p;
minlen = sizeof(struct dentry) + NAME_ALIGN_LEN(de->namelen);
if (de->reclen >= minlen + newlen) {
newde = (struct dentry *) (p + minlen);
newde->ino = ino;
newde->reclen = de->reclen - minlen;
newde->namelen = len;
memcpy(newde->name, name, len);
de->reclen = minlen;
mark_buffer_updated(dir->fs->cache, buf);
release_buffer(dir->fs->cache, buf);
dir->desc->mtime = time(NULL);
mark_inode_dirty(dir);
return 0;
}
p += de->reclen;
}
release_buffer(dir->fs->cache, buf);
}
blk = expand_inode(dir);
if (blk == NOBLOCK) return -ENOSPC;
buf = alloc_buffer(dir->fs->cache, blk);
if (!buf) return -ENOMEM;
dir->desc->size += dir->fs->blocksize;
dir->desc->mtime = time(NULL);
mark_inode_dirty(dir);
newde = (struct dentry *) (buf->data);
newde->ino = ino;
newde->reclen = dir->fs->blocksize;
newde->namelen = len;
memcpy(newde->name, name, len);
mark_buffer_updated(dir->fs->cache, buf);
release_buffer(dir->fs->cache, buf);
return 0;
}
int modify_dir_entry(struct inode *dir, char *name, int len, ino_t ino, ino_t *oldino) {
unsigned int block;
blkno_t blk;
struct buf *buf;
char *p;
struct dentry *de;
if (len <= 0) return -EINVAL;
if (len >= MAXPATH) return -ENAMETOOLONG;
if (!S_ISDIR(dir->desc->mode)) return -ENOTDIR;
if (checki(dir, S_IWRITE) < 0) return -EACCES;
for (block = 0; block < dir->desc->blocks; block++) {
blk = get_inode_block(dir, block);
if (blk == NOBLOCK) return -EIO;
buf = get_buffer(dir->fs->cache, blk);
if (!buf) return -EIO;
p = buf->data;
while (p < buf->data + dir->fs->blocksize) {
de = (struct dentry *) p;
if (fnmatch(name, len, de->name, de->namelen)) {
if (oldino) *oldino = de->ino;
de->ino = ino;
mark_buffer_updated(dir->fs->cache, buf);
release_buffer(dir->fs->cache, buf);
return 0;
}
p += de->reclen;
}
release_buffer(dir->fs->cache, buf);
}
return -ENOENT;
}
int delete_dir_entry(struct inode *dir, char *name, int len) {
unsigned int block;
blkno_t blk;
blkno_t lastblk;
struct buf *buf;
char *p;
struct dentry *de;
struct dentry *prevde;
struct dentry *nextde;
if (len <= 0 || len >= MAXPATH) return -ENAMETOOLONG;
if (!S_ISDIR(dir->desc->mode)) return -ENOTDIR;
if (checki(dir, S_IWRITE) < 0) return -EACCES;
for (block = 0; block < dir->desc->blocks; block++) {
blk = get_inode_block(dir, block);
if (blk == NOBLOCK) return -EIO;
buf = get_buffer(dir->fs->cache, blk);
if (!buf) return -EIO;
p = buf->data;
prevde = NULL;
while (p < buf->data + dir->fs->blocksize) {
de = (struct dentry *) p;
if (fnmatch(name, len, de->name, de->namelen)) {
if (prevde) {
// Merge entry with previous entry
prevde->reclen += de->reclen;
memset(de, 0, de->reclen);
mark_buffer_updated(dir->fs->cache, buf);
} else if (de->reclen == dir->fs->blocksize) {
// Block is empty, swap this block with last block and truncate
if (block != dir->desc->blocks - 1) {
lastblk = get_inode_block(dir, dir->desc->blocks - 1);
if (lastblk == NOBLOCK) return -EIO;
set_inode_block(dir, block, lastblk);
set_inode_block(dir, dir->desc->blocks - 1, buf->blkno);
}
truncate_inode(dir, dir->desc->blocks - 1);
dir->desc->size -= dir->fs->blocksize;
mark_buffer_invalid(dir->fs->cache, buf);
} else {
// Merge with next entry
nextde = (struct dentry *) (p + de->reclen);
de->ino = nextde->ino;
de->reclen += nextde->reclen;
de->namelen = nextde->namelen;
memmove(de->name, nextde->name, nextde->namelen);
mark_buffer_updated(dir->fs->cache, buf);
}
release_buffer(dir->fs->cache, buf);
dir->desc->mtime = time(NULL);
mark_inode_dirty(dir);
return 0;
}
prevde = de;
p += de->reclen;
}
release_buffer(dir->fs->cache, buf);
}
return -ENOENT;
}
static int lookup_name(struct filsys *fs, ino_t ino, char *name, int len, ino_t *retval) {
char *p;
int l;
struct inode *inode;
int rc;
while (1) {
// Skip path separator
if (*name == PS1 || *name == PS2) {
name++;
len--;
}
if (len == 0) {
*retval = ino;
return 0;
}
// Find next part of name
p = name;
l = 0;
while (l < len && *p != PS1 && *p != PS2) {
l++;
p++;
}
// Find inode for next name part
rc = get_inode(fs, ino, &inode);
if (rc < 0) return rc;
rc = find_dir_entry(inode, name, l, &ino);
release_inode(inode);
if (rc < 0) return rc;
// If we have parsed the whole name return the inode number
if (l == len) {
*retval = ino;
return 0;
}
// Prepare for next name part
name = p;
len -= l;
}
}
int diri(struct filsys *fs, char **name, int *len, struct inode **inode) {
char *start;
char *p;
ino_t ino;
struct inode *dir;
int rc;
if (*len == 0) return get_inode(fs, DFS_INODE_ROOT, inode);
start = *name;
p = start + *len - 1;
while (p > start && *p != PS1 && *p != PS2) p--;
if (p == start) {
if (*p == PS1 || *p == PS2) {
(*name)++;
(*len)--;
}
return get_inode(fs, DFS_INODE_ROOT, inode);
}
rc = lookup_name(fs, DFS_INODE_ROOT, start, p - start, &ino);
if (rc < 0) return rc;
rc = get_inode(fs, ino, &dir);
if (rc < 0) return rc;
if (!S_ISDIR(dir->desc->mode)) {
release_inode(dir);
return -ENOTDIR;
}
*name = p + 1;
*len -= p - start + 1;
*inode = dir;
return 0;
}
int namei(struct filsys *fs, char *name, struct inode **inode) {
ino_t ino;
int rc;
rc = lookup_name(fs, DFS_INODE_ROOT, name, strlen(name), &ino);
if (rc < 0) return rc;
rc = get_inode(fs, ino, inode);
if (rc < 0) return rc;
return 0;
}
int dfs_opendir(struct file *filp, char *name) {
struct inode *inode;
int rc;
rc = namei((struct filsys *) filp->fs->data, name, &inode);
if (rc < 0) return rc;
if (!S_ISDIR(inode->desc->mode)) {
release_inode(inode);
return -ENOTDIR;
}
filp->data = inode;
filp->mode = inode->desc->mode;
filp->owner = inode->desc->uid;
filp->group = inode->desc->gid;
return 0;
}
int dfs_readdir(struct file *filp, struct direntry *dirp, int count) {
unsigned int iblock;
unsigned int start;
struct inode *inode;
blkno_t blk;
struct buf *buf;
struct dentry *de;
inode = (struct inode *) filp->data;
if (count != 1) return -EINVAL;
if (filp->pos == inode->desc->size) return 0;
if (filp->pos > inode->desc->size) return 0;
iblock = (unsigned int) filp->pos / inode->fs->blocksize;
start = (unsigned int) filp->pos % inode->fs->blocksize;
blk = get_inode_block(inode, iblock);
if (blk == NOBLOCK) return -EIO;
buf = get_buffer(inode->fs->cache, blk);
if (!buf) return -EIO;
de = (struct dentry *) (buf->data + start);
if (de->reclen + start > inode->fs->blocksize || de->namelen <= 0 || de->namelen >= MAXPATH) {
release_buffer(inode->fs->cache, buf);
return 0;
}
dirp->ino = de->ino;
dirp->reclen = de->reclen;
dirp->namelen = de->namelen;
memcpy(dirp->name, de->name, de->namelen);
dirp->name[de->namelen] = 0;
filp->pos += de->reclen;
release_buffer(inode->fs->cache, buf);
return 1;
}