Goto sanos source index
//
// file.c
//
// Disk filesystem file 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>
static int open_existing(struct filsys *fs, char *name, struct inode **retval) {
struct inode *inode;
int rc;
rc = namei(fs, name, &inode);
if (rc < 0) return rc;
*retval = inode;
return 0;
}
static int open_always(struct filsys *fs, char *name, int mode, struct inode **retval) {
struct inode *dir;
struct inode *inode;
ino_t ino;
int rc;
int len = strlen(name);
rc = diri(fs, &name, &len, &dir);
if (rc < 0) return rc;
rc = find_dir_entry(dir, name, len, &ino);
if (rc < 0 && rc != -ENOENT) {
release_inode(dir);
return rc;
}
if (rc == -ENOENT) {
inode = alloc_inode(dir, S_IFREG | (mode & S_IRWXUGO));
if (!inode) {
release_inode(dir);
return -ENOSPC;
}
rc = add_dir_entry(dir, name, len, inode->ino);
if (rc < 0) {
unlink_inode(inode);
release_inode(inode);
release_inode(dir);
return rc;
}
} else {
rc = get_inode(fs, ino, &inode);
if (rc < 0) {
release_inode(dir);
return rc;
}
}
release_inode(dir);
*retval = inode;
return 0;
}
static int create_always(struct filsys *fs, char *name, int mode, struct inode **retval) {
struct inode *dir;
struct inode *inode;
struct inode *oldinode;
ino_t oldino;
int rc;
int len = strlen(name);
rc = diri(fs, &name, &len, &dir);
if (rc < 0) return rc;
rc = find_dir_entry(dir, name, len, &oldino);
if (rc == 0) {
rc = get_inode(fs, oldino, &oldinode);
if (rc < 0) {
release_inode(dir);
return rc;
}
if (S_ISDIR(oldinode->desc->mode) && oldinode->desc->linkcount == 1) {
release_inode(dir);
return -EISDIR;
}
inode = alloc_inode(dir, S_IFREG | (mode & S_IRWXUGO));
if (!inode) {
release_inode(dir);
return -ENOSPC;
}
rc = modify_dir_entry(dir, name, len, inode->ino, NULL);
if (rc < 0) {
unlink_inode(inode);
release_inode(inode);
release_inode(dir);
return rc;
}
unlink_inode(oldinode);
release_inode(oldinode);
} else if (rc == -ENOENT) {
inode = alloc_inode(dir, S_IFREG | (mode & S_IRWXUGO));
if (!inode) {
release_inode(dir);
return -ENOSPC;
}
rc = add_dir_entry(dir, name, len, inode->ino);
if (rc < 0) {
unlink_inode(inode);
release_inode(inode);
release_inode(dir);
return rc;
}
} else {
release_inode(dir);
return rc;
}
release_inode(dir);
*retval = inode;
return 0;
}
static int truncate_existing(struct filsys *fs, char *name, struct inode **retval) {
struct inode *inode;
int rc;
rc = namei(fs, name, &inode);
if (rc < 0) return rc;
if (S_ISDIR(inode->desc->mode)) {
release_inode(inode);
return -EISDIR;
}
rc = truncate_inode(inode, 0);
if (rc < 0) {
release_inode(inode);
return rc;
}
inode->desc->size = 0;
mark_inode_dirty(inode);
*retval = inode;
return 0;
}
static int create_new(struct filsys *fs, char *name, ino_t ino, int mode, struct inode **retval) {
struct inode *dir;
struct inode *inode;
int rc;
int len = strlen(name);
rc = diri(fs, &name, &len, &dir);
if (rc < 0) return rc;
rc = find_dir_entry(dir, name, len, NULL);
if (rc != -ENOENT) {
release_inode(dir);
return rc >= 0 ? -EEXIST : rc;
}
if (ino == NOINODE) {
inode = alloc_inode(dir, S_IFREG | (mode & S_IRWXUGO));
if (!inode) rc = -ENOSPC;
} else {
rc = get_inode(fs, ino, &inode);
if (rc < 0) inode = NULL;
if (inode) {
inode->desc->ctime = inode->desc->mtime = time(NULL);
inode->desc->uid = inode->desc->gid = 0;
inode->desc->mode = S_IFREG | 0700;
inode->desc->linkcount++;
mark_inode_dirty(inode);
}
}
if (!inode) {
release_inode(dir);
return rc;
}
rc = add_dir_entry(dir, name, len, inode->ino);
if (rc < 0) {
unlink_inode(inode);
release_inode(inode);
release_inode(dir);
return rc;
}
release_inode(dir);
*retval = inode;
return 0;
}
int dfs_open(struct file *filp, char *name) {
struct filsys *fs;
struct inode *inode;
int rc;
fs = (struct filsys *) filp->fs->data;
switch (filp->flags & (O_CREAT | O_EXCL | O_TRUNC | O_SPECIAL)) {
case 0:
case O_EXCL:
// Open existing file
rc = open_existing(fs, name, &inode);
break;
case O_CREAT:
// Open file, create new file if it does not exists
rc = open_always(fs, name, filp->mode, &inode);
break;
case O_CREAT | O_EXCL:
case O_CREAT | O_TRUNC | O_EXCL:
// Create new file, fail if it exists
rc = create_new(fs, name, NOINODE, filp->mode, &inode);
filp->flags |= F_MODIFIED;
break;
case O_TRUNC:
case O_TRUNC | O_EXCL:
// Open and truncate existing file
rc = truncate_existing(fs, name, &inode);
filp->flags |= F_MODIFIED;
break;
case O_CREAT | O_TRUNC:
// Create new file, unlink existing file if it exists
rc = create_always(fs, name, filp->mode, &inode);
filp->flags |= F_MODIFIED;
break;
case O_SPECIAL:
// Create new file with special inode number
rc = create_new(fs, name, filp->flags >> 24, filp->mode, &inode);
filp->flags |= F_MODIFIED;
break;
default:
return -EINVAL;
}
if (rc < 0) return rc;
if (filp->flags & O_APPEND) filp->pos = inode->desc->size;
filp->data = inode;
filp->mode = inode->desc->mode;
filp->owner = inode->desc->uid;
filp->group = inode->desc->gid;
return 0;
}
int dfs_close(struct file *filp) {
struct inode *inode;
inode = (struct inode *) filp->data;
if (filp->flags & F_MODIFIED) {
inode->desc->mtime = time(NULL);
mark_inode_dirty(inode);
}
if (filp->flags & O_TEMPORARY) unlink(filp->path);
return 0;
}
int dfs_destroy(struct file *filp) {
struct inode *inode;
inode = (struct inode *) filp->data;
release_inode(inode);
return 0;
}
int dfs_fsync(struct file *filp) {
int rc;
struct inode *inode = (struct inode *) filp->data;
// Flush and sync buffer cache for entire file system
rc = flush_buffers(inode->fs->cache, 0);
if (rc < 0) return rc;
rc = sync_buffers(inode->fs->cache, 0);
if (rc < 0) return rc;
return 0;
}
int dfs_read(struct file *filp, void *data, size_t size, off64_t pos) {
struct inode *inode;
size_t read;
size_t count;
off64_t left;
char *p;
unsigned int iblock;
unsigned int start;
blkno_t blk;
struct buf *buf;
inode = (struct inode *) filp->data;
read = 0;
p = (char *) data;
while (pos < inode->desc->size && size > 0) {
if (filp->flags & F_CLOSED) return -EINTR;
iblock = (unsigned int) (pos / inode->fs->blocksize);
start = (unsigned int) (pos % inode->fs->blocksize);
count = inode->fs->blocksize - start;
if (count > size) count = size;
left = inode->desc->size - (size_t) pos;
if (count > left) count = (size_t) left;
if (count <= 0) break;
blk = get_inode_block(inode, iblock);
if (blk == NOBLOCK) return -EIO;
if (filp->flags & O_DIRECT) {
if (start != 0 || count != inode->fs->blocksize) return read;
if (dev_read(inode->fs->devno, p, count, blk, 0) != (int) count) return read;
} else {
buf = get_buffer(inode->fs->cache, blk);
if (!buf) return -EIO;
memcpy(p, buf->data + start, count);
release_buffer(inode->fs->cache, buf);
}
pos += count;
p += count;
read += count;
size -= count;
}
return read;
}
int dfs_write(struct file *filp, void *data, size_t size, off64_t pos) {
struct inode *inode;
size_t written;
size_t count;
char *p;
unsigned int iblock;
unsigned int start;
blkno_t blk;
struct buf *buf;
int rc;
inode = (struct inode *) filp->data;
if (filp->flags & O_APPEND) pos = inode->desc->size;
if (pos + size > DFS_MAXFILESIZE) return -EFBIG;
if (S_ISDIR(inode->desc->mode)) return -EISDIR;
if (pos > inode->desc->size) {
rc = dfs_ftruncate(filp, pos);
if (rc < 0) return rc;
}
written = 0;
p = (char *) data;
while (size > 0) {
if (filp->flags & F_CLOSED) return -EINTR;
iblock = (unsigned int) pos / inode->fs->blocksize;
start = (unsigned int) pos % inode->fs->blocksize;
count = inode->fs->blocksize - start;
if (count > size) count = size;
if (iblock < inode->desc->blocks) {
blk = get_inode_block(inode, iblock);
if (blk == NOBLOCK) return -EIO;
} else if (iblock == inode->desc->blocks) {
blk = expand_inode(inode);
if (blk == NOBLOCK) return -ENOSPC;
} else {
return written;
}
if (filp->flags & O_DIRECT) {
if (start != 0 || count != inode->fs->blocksize) return written;
if (dev_write(inode->fs->devno, p, count, blk, 0) != (int) count) return written;
} else {
if (count == inode->fs->blocksize) {
buf = alloc_buffer(inode->fs->cache, blk);
} else {
buf = get_buffer(inode->fs->cache, blk);
}
if (!buf) return -EIO;
memcpy(buf->data + start, p, count);
mark_buffer_updated(inode->fs->cache, buf);
release_buffer(inode->fs->cache, buf);
}
filp->flags |= F_MODIFIED;
pos += count;
p += count;
written += count;
size -= count;
if (pos > inode->desc->size) {
inode->desc->size = pos;
mark_inode_dirty(inode);
}
}
return written;
}
int dfs_ioctl(struct file *filp, int cmd, void *data, size_t size) {
return -ENOSYS;
}
off64_t dfs_tell(struct file *filp) {
return filp->pos;
}
off64_t dfs_lseek(struct file *filp, off64_t offset, int origin) {
struct inode *inode;
inode = (struct inode *) filp->data;
switch (origin) {
case SEEK_END:
offset += inode->desc->size;
break;
case SEEK_CUR:
offset += filp->pos;
}
if (offset < 0) return -EINVAL;
filp->pos = offset;
return offset;
}
int dfs_ftruncate(struct file *filp, off64_t size) {
struct inode *inode;
int rc;
unsigned int blocks;
blkno_t blk;
struct buf *buf;
if (size > DFS_MAXFILESIZE) return -EFBIG;
inode = (struct inode *) filp->data;
if (S_ISDIR(inode->desc->mode)) return -EISDIR;
if (size < 0) return -EINVAL;
if (size == inode->desc->size) return 0;
blocks = ((size_t) size + inode->fs->blocksize - 1) / inode->fs->blocksize;
if (size > inode->desc->size) {
while (inode->desc->blocks < blocks) {
blk = expand_inode(inode);
if (blk == NOBLOCK) return -ENOSPC;
buf = alloc_buffer(inode->fs->cache, blk);
if (!buf) return -EIO;
memset(buf->data, 0, inode->fs->blocksize);
mark_buffer_updated(inode->fs->cache, buf);
release_buffer(inode->fs->cache, buf);
}
} else {
rc = truncate_inode(inode, blocks);
if (rc < 0) return rc;
}
inode->desc->size = size;
mark_inode_dirty(inode);
filp->flags |= F_MODIFIED;
return 0;
}
int dfs_futime(struct file *filp, struct utimbuf *times) {
struct inode *inode;
inode = (struct inode *) filp->data;
if (times->ctime != -1) inode->desc->ctime = times->ctime;
if (times->modtime != -1) inode->desc->mtime = times->modtime;
filp->flags &= ~F_MODIFIED;
mark_inode_dirty(inode);
return 0;
}
int dfs_fstat(struct file *filp, struct stat64 *buffer) {
struct inode *inode;
off64_t size;
inode = (struct inode *) filp->data;
size = inode->desc->size;
if (buffer) {
memset(buffer, 0, sizeof(struct stat64));
buffer->st_mode = inode->desc->mode;
buffer->st_uid = inode->desc->uid;
buffer->st_gid = inode->desc->gid;
buffer->st_ino = inode->ino;
buffer->st_nlink = inode->desc->linkcount;
buffer->st_dev = inode->fs->devno;
buffer->st_atime = time(NULL);
buffer->st_mtime = inode->desc->mtime;
buffer->st_ctime = inode->desc->ctime;
buffer->st_size = inode->desc->size;
}
return (int) size;
}
int dfs_fchmod(struct file *filp, int mode) {
struct thread *thread = self();
struct inode *inode;
inode = (struct inode *) filp->data;
if (thread->euid != 0 && thread->euid != inode->desc->uid) return -EPERM;
inode->desc->mode = (inode->desc->mode & ~S_IRWXUGO) | (mode & S_IRWXUGO);
mark_inode_dirty(inode);
return 0;
}
int dfs_fchown(struct file *filp, int owner, int group) {
struct thread *thread = self();
struct inode *inode;
inode = (struct inode *) filp->data;
if (thread->euid != 0) return -EPERM;
if (owner != -1) inode->desc->uid = owner;
if (group != -1) inode->desc->gid = group;
mark_inode_dirty(inode);
return 0;
}