/* * Removes an entry from a directory. */ PUBLIC int dir_remove(struct inode *dinode, const char *filename) { struct buffer *buf; /* Block buffer. */ struct d_dirent *d; /* Directory entry. */ struct inode *file; /* File inode. */ d = dirent_search(dinode, filename, &buf, 0); /* Not found. */ if (d == NULL) return (-ENOENT); /* Cannot remove '.' */ if (d->d_ino == dinode->num) { brelse(buf); return (-EBUSY); } file = inode_get(dinode->dev, d->d_ino); /* Failed to get file's inode. */ if (file == NULL) { brelse(buf); return (-ENOENT); } /* Unlinking directory. */ if (S_ISDIR(file->mode)) { /* Not allowed. */ if (!IS_SUPERUSER(curr_proc)) { inode_put(file); brelse(buf); return (-EPERM); } /* Directory not empty. */ if (dinode->size) { inode_put(file); brelse(buf); return (-EBUSY); } } /* Remove directory entry. */ d->d_ino = INODE_NULL; buf->flags |= BUFFER_DIRTY; inode_touch(dinode); file->nlinks--; inode_touch(file); inode_put(file); brelse(buf); return (0); }
/** * @brief Create an indirect block. * * @details Creates a indirect block and saves in @param dest. * * @param dest Destination buffer, It is the corresponding disk block to be changed. * @param ip File to use. * @param offset Offset to be calculated the block. * @param creat If 1, creates an block, otherwise, just return this value. * * @returns If successful, the block number created / obtained, otherwise BLOCK_NULL is * returned. * * @note @p ip must be locked. */ PUBLIC block_t create_indirect_block (struct buffer *dest, struct inode *ip, off_t offset, int create) { block_t phys; /* Physical block number. */ if (((block_t *)buffer_data(dest))[offset] == BLOCK_NULL && create) { /* Allocate an block. */ superblock_lock(ip->sb); phys = block_alloc(ip->sb); superblock_unlock(ip->sb); if (phys != BLOCK_NULL) { ((block_t *)buffer_data(dest))[offset] = phys; buffer_dirty(dest, 1); inode_touch(ip); brelse(dest); return (phys); } else { brelse(dest); return (phys); } } else { brelse(dest); return ((block_t *)buffer_data(dest))[offset]; } }
enum nfsstat4 dir_add(DB_TXN *txn, struct nfs_inode *dir_ino, const struct nfs_buf *name_in, struct nfs_inode *ent_ino) { enum nfsstat4 status = NFS4_OK, lu_stat; int rc; lu_stat = dir_lookup(txn, dir_ino, name_in, 0, NULL); if (lu_stat != NFS4ERR_NOENT) { if (lu_stat == NFS4_OK) status = NFS4ERR_EXIST; else status = lu_stat; return status; } rc = fsdb_dirent_put(&srv.fsdb, txn, dir_ino->inum, name_in, DB_NOOVERWRITE, ent_ino->inum); if (rc) { if (rc == DB_KEYEXIST) status = NFS4ERR_EXIST; else status = NFS4ERR_IO; goto out; } rc = inode_touch(txn, dir_ino); if (rc) { status = NFS4ERR_IO; goto out; } out: return status; }
/* * Reads from a regular file. */ PUBLIC ssize_t file_read(struct inode *i, void *buf, size_t n, off_t off) { char *p; /* Writing pointer. */ size_t blkoff; /* Block offset. */ size_t chunk; /* Data chunk size. */ block_t blk; /* Working block number. */ struct buffer *bbuf; /* Working block buffer. */ p = buf; inode_lock(i); /* Read data. */ do { blk = block_map(i, off, 0); /* End of file reached. */ if (blk == BLOCK_NULL) goto out; bbuf = bread(i->dev, blk); blkoff = off % BLOCK_SIZE; /* Calculate read chunk size. */ chunk = (n < BLOCK_SIZE - blkoff) ? n : BLOCK_SIZE - blkoff; if ((off_t)chunk > i->size - off) { chunk = i->size - off; if (chunk == 0) { brelse(bbuf); goto out; } } kmemcpy(p, (char *)bbuf->data + blkoff, chunk); brelse(bbuf); n -= chunk; off += chunk; p += chunk; } while (n > 0); out: inode_touch(i); inode_unlock(i); return ((ssize_t)(p - (char *)buf)); }
/* * Writes to a regular file. */ PUBLIC ssize_t file_write(struct inode *i, const void *buf, size_t n, off_t off) { const char *p; /* Reading pointer. */ size_t blkoff; /* Block offset. */ size_t chunk; /* Data chunk size. */ block_t blk; /* Working block number. */ struct buffer *bbuf; /* Working block buffer. */ p = buf; inode_lock(i); /* Write data. */ do { blk = block_map(i, off, 1); /* End of file reached. */ if (blk == BLOCK_NULL) goto out; bbuf = bread(i->dev, blk); blkoff = off % BLOCK_SIZE; chunk = (n < BLOCK_SIZE - blkoff) ? n : BLOCK_SIZE - blkoff; kmemcpy((char *)bbuf->data + blkoff, buf, chunk); bbuf->flags |= BUFFER_DIRTY; brelse(bbuf); n -= chunk; off += chunk; p += chunk; /* Update file size. */ if (off > i->size) { i->size = off; i->flags |= INODE_DIRTY; } } while (n > 0); out: inode_touch(i); inode_unlock(i); return ((ssize_t)(p - (char *)buf)); }
/** * @brief Create an direct block. * * @details Creates a direct block and saves in @param dest. * * @param ip File to use. * @param offset Offset to be calculated the block. * @param creat If 1, creates an block, otherwise, just return this value. * * @returns If successful, the block number created/obtained, otherwise BLOCK_NULL is * returned. * * @note @p ip must be locked. */ PUBLIC block_t create_direct_block(struct inode *ip, off_t offset, int create) { block_t phys; /* Physical block number. */ if (ip->blocks[offset] == BLOCK_NULL && create) { /* Allocate an block. */ superblock_lock(ip->sb); phys = block_alloc(ip->sb); superblock_unlock(ip->sb); if (phys != BLOCK_NULL) { ip->blocks[offset] = phys; inode_touch(ip); return (phys); } else return (phys); } else return (ip->blocks[offset]); }
/** * @brief Maps a file byte offset in a disk block number. * * @details Maps the offset @p off in the file pointed to by @p ip in a disk * block number. If @p create is not zero and such file by offset is * invalid, the file is expanded accordingly to make it valid. * * @param ip File to use * @param off File byte offset. * @param create Create offset? * * @returns Upon successful completion, the disk block number that is associated * with the file byte offset is returned. Upon failure, #BLOCK_NULL is * returned instead. * * @note @p ip must be locked. */ PUBLIC block_t block_map(struct inode *ip, off_t off, int create) { block_t phys; /* Physical block number. */ block_t logic; /* Logical block number. */ struct buffer *buf; /* Underlying buffer. */ unsigned tmp; /* Logical block number tmp. */ logic = off/BLOCK_SIZE; /* File offset too big. */ if (off >= ip->sb->max_size) { curr_proc->errno = -EFBIG; return (BLOCK_NULL); } /* * Create blocks that are * in a valid offset. */ if (off < ip->size) create = 1; /* Direct block. */ if (logic < NR_ZONES_DIRECT) { /* Create direct block. */ if (ip->blocks[logic] == BLOCK_NULL && create) { superblock_lock(ip->sb); phys = block_alloc(ip->sb); superblock_unlock(ip->sb); if (phys != BLOCK_NULL) { ip->blocks[logic] = phys; inode_touch(ip); } } return (ip->blocks[logic]); } logic -= NR_ZONES_DIRECT; /* Single indirect block. */ if (logic < NR_SINGLE) { /* Create single indirect block. */ if (ip->blocks[ZONE_SINGLE] == BLOCK_NULL && create) { superblock_lock(ip->sb); phys = block_alloc(ip->sb); superblock_unlock(ip->sb); if (phys != BLOCK_NULL) { ip->blocks[ZONE_SINGLE] = phys; inode_touch(ip); } } /* We cannot go any further. */ if ((phys = ip->blocks[ZONE_SINGLE]) == BLOCK_NULL) return (BLOCK_NULL); buf = bread(ip->dev, phys); /* Create direct block. */ if (((block_t *)buffer_data(buf))[logic] == BLOCK_NULL && create) { superblock_lock(ip->sb); phys = block_alloc(ip->sb); superblock_unlock(ip->sb); if (phys != BLOCK_NULL) { ((block_t *)buffer_data(buf))[logic] = phys; buffer_dirty(buf, 1); inode_touch(ip); } } brelse(buf); return (((block_t *)buffer_data(buf))[logic]); } logic = off - REMAINING_OFFSET; /* Double indirect block. */ tmp = logic / BLOCK_SIZE; if (tmp < NR_DOUBLE) { size_t logicSingle = logic / (NR_SINGLE * BLOCK_SIZE); size_t logicDouble = logic / BLOCK_SIZE; /* Create single, double and/or direct block. */ if ( (phys = create_direct_block(ip,ZONE_DOUBLE,create)) != BLOCK_NULL) { buf = bread(ip->dev, phys); if ( (phys = create_indirect_block(buf,ip,logicSingle,create)) != BLOCK_NULL) { buf = bread(ip->dev, phys); if ( (phys = create_indirect_block(buf,ip,logicDouble,create)) != BLOCK_NULL) return (phys); else return (BLOCK_NULL); } else return (BLOCK_NULL); } else return (BLOCK_NULL); } /* Triple indirect zone. */ kpanic("triple indirect zones not supported"); return (BLOCK_NULL); }
/* * Reads from a file. */ PUBLIC ssize_t sys_read(int fd, void *buf, size_t n) { dev_t dev; /* Device number. */ struct file *f; /* File. */ struct inode *i; /* Inode. */ ssize_t count; /* Bytes actually read. */ /* Invalid file descriptor. */ if ((fd < 0) || (fd >= OPEN_MAX) || ((f = curr_proc->ofiles[fd]) == NULL)) return (-EBADF); /* File not opened for reading. */ if (ACCMODE(f->oflag) == O_WRONLY) return (-EBADF); #if (EDUCATIONAL_KERNEL == 0) /* Invalid buffer. */ if (!chkmem(buf, n, MAY_WRITE)) return (-EINVAL); #endif /* Nothing to do. */ if (n == 0) return (0); i = f->inode; /* Character special file. */ if (S_ISCHR(i->mode)) { dev = i->blocks[0]; count = cdev_read(dev, buf, n); return (count); } /* Block special file. */ else if (S_ISBLK(i->mode)) { dev = i->blocks[0]; count = bdev_read(dev, buf, n, f->pos); } /* Pipe file. */ else if (S_ISFIFO(i->mode)) { kprintf("read from pipe"); count = pipe_read(i, buf, n); } /* Regular file/directory. */ else if ((S_ISDIR(i->mode)) || (S_ISREG(i->mode))) count = file_read(i, buf, n, f->pos); /* Unknown file type. */ else return (-EINVAL); /* Failed to read. */ if (count < 0) return (curr_proc->errno); inode_touch(i); f->pos += count; return (count); }
nfsstat4 nfs_op_rename(struct nfs_cxn *cxn, const RENAME4args *args, struct list_head *writes, struct rpc_write **wr) { nfsstat4 status = NFS4_OK; struct nfs_inode *src_dir = NULL, *target_dir = NULL; struct nfs_inode *old_file = NULL, *new_file = NULL; struct nfs_buf oldname, newname; change_info4 src = { true, 0, 0 }; change_info4 target = { true, 0, 0 }; DB_TXN *txn = NULL; DB_ENV *dbenv = srv.fsdb.env; int rc; nfsino_t old_dirent, new_dirent; oldname.len = args->oldname.utf8string_len; oldname.val = args->oldname.utf8string_val; newname.len = args->newname.utf8string_len; newname.val = args->newname.utf8string_val; if (debugging) applog(LOG_INFO, "op RENAME (OLD:%.*s, NEW:%.*s)", oldname.len, oldname.val, newname.len, newname.val); /* validate text input */ if ((!valid_utf8string(&oldname)) || (!valid_utf8string(&newname))) { status = NFS4ERR_INVAL; goto out; } if (has_dots(&oldname) || has_dots(&newname)) { status = NFS4ERR_BADNAME; goto out; } rc = dbenv->txn_begin(dbenv, NULL, &txn, 0); if (rc) { status = NFS4ERR_IO; dbenv->err(dbenv, rc, "DB_ENV->txn_begin"); goto out; } /* reference source, target directories. * NOTE: src_dir and target_dir may point to the same object */ src_dir = inode_fhdec(txn, cxn->save_fh, DB_RMW); if (fh_equal(cxn->save_fh, cxn->current_fh)) target_dir = src_dir; else target_dir = inode_fhdec(txn, cxn->current_fh, DB_RMW); if (!src_dir || !target_dir) { status = NFS4ERR_NOFILEHANDLE; goto out_abort; } if ((src_dir->type != NF4DIR) || (target_dir->type != NF4DIR)) { status = NFS4ERR_NOTDIR; goto out_abort; } /* lookup source, target names */ status = dir_lookup(txn, src_dir, &oldname, 0, &old_dirent); if (status != NFS4_OK) goto out_abort; old_file = inode_getdec(txn, old_dirent, 0); if (!old_file) { status = NFS4ERR_NOENT; goto out_abort; } status = dir_lookup(txn, target_dir, &newname, 0, &new_dirent); if (status != NFS4_OK && status != NFS4ERR_NOENT) goto out_abort; /* if target (newname) is present, attempt to remove */ if (status == NFS4_OK) { bool ok_to_remove = false; /* read to-be-deleted inode */ new_file = inode_getdec(txn, new_dirent, DB_RMW); if (!new_file) { status = NFS4ERR_NOENT; goto out_abort; } /* do oldname and newname refer to same file? */ if (old_file->inum == new_file->inum) { src.after = src.before = src_dir->version; target.after = target.before = target_dir->version; goto out_abort; } if (old_file->type != NF4DIR && new_file->type != NF4DIR) ok_to_remove = true; else if (old_file->type == NF4DIR && new_file->type == NF4DIR && dir_is_empty(txn, new_file)) ok_to_remove = true; if (!ok_to_remove) { status = NFS4ERR_EXIST; goto out_abort; } /* remove target inode from directory */ rc = fsdb_dirent_del(&srv.fsdb, txn, target_dir->inum, &newname, 0); if (rc == 0) rc = inode_unlink(txn, new_file); if (rc) { status = NFS4ERR_IO; goto out_abort; } } else status = NFS4_OK; new_dirent = old_dirent; /* delete entry from source directory; add to target directory */ rc = fsdb_dirent_del(&srv.fsdb, txn, src_dir->inum, &oldname, 0); if (rc == 0) rc = fsdb_dirent_put(&srv.fsdb, txn, target_dir->inum, &newname, 0, new_dirent); if (rc) { status = NFS4ERR_IO; goto out_abort; } /* if renamed file is a directory, ensure its 'parent' is updated */ if (old_file->type == NF4DIR) { old_file->parent = target_dir->inum; if (inode_touch(txn, old_file)) { status = NFS4ERR_IO; goto out_abort; } } /* record directory change info */ src.before = src_dir->version; target.before = target_dir->version; /* update last-modified stamps of directory inodes */ rc = inode_touch(txn, src_dir); if (rc == 0 && src_dir != target_dir) rc = inode_touch(txn, target_dir); if (rc) { status = NFS4ERR_IO; goto out_abort; } /* close the transaction */ rc = txn->commit(txn, 0); if (rc) { dbenv->err(dbenv, rc, "DB_ENV->txn_commit"); status = NFS4ERR_IO; goto out; } src.after = src_dir->version; target.after = target_dir->version; out: WR32(status); if (status == NFS4_OK) { WR32(src.atomic ? 1 : 0); /* src cinfo.atomic */ WR64(src.before); /* src cinfo.before */ WR64(src.after); /* src cinfo.after */ WR32(target.atomic ? 1 : 0); /* target cinfo.atomic */ WR64(target.before); /* target cinfo.before */ WR64(target.after); /* target cinfo.after */ } inode_free(src_dir); if (src_dir != target_dir) inode_free(target_dir); inode_free(old_file); inode_free(new_file); return status; out_abort: if (txn->abort(txn)) dbenv->err(dbenv, rc, "DB_ENV->txn_abort"); goto out; }
nfsstat4 nfs_op_remove(struct nfs_cxn *cxn, const REMOVE4args *args, struct list_head *writes, struct rpc_write **wr) { nfsstat4 status = NFS4_OK; struct nfs_inode *dir_ino = NULL, *target_ino = NULL; struct nfs_buf target; change_info4 cinfo = { true, 0, 0 }; DB_TXN *txn = NULL; DB_ENV *dbenv = srv.fsdb.env; int rc; nfsino_t de_inum; target.len = args->target.utf8string_len; target.val = args->target.utf8string_val; if (debugging) applog(LOG_INFO, "op REMOVE ('%.*s')", target.len, target.val); if (target.len > SRV_MAX_NAME) { status = NFS4ERR_NAMETOOLONG; goto out; } if (!valid_utf8string(&target)) { status = NFS4ERR_INVAL; goto out; } if (has_dots(&target)) { status = NFS4ERR_BADNAME; goto out; } rc = dbenv->txn_begin(dbenv, NULL, &txn, 0); if (rc) { status = NFS4ERR_IO; dbenv->err(dbenv, rc, "DB_ENV->txn_begin"); goto out; } /* reference container directory */ status = dir_curfh(txn, cxn, &dir_ino, DB_RMW); if (status != NFS4_OK) goto out_abort; /* lookup target name in directory */ status = dir_lookup(txn, dir_ino, &target, 0, &de_inum); if (status != NFS4_OK) goto out_abort; /* reference target inode */ target_ino = inode_getdec(txn, de_inum, DB_RMW); if (!target_ino) { status = NFS4ERR_NOENT; goto out_abort; } /* prevent root dir deletion */ if (target_ino->inum == INO_ROOT) { status = NFS4ERR_INVAL; goto out_abort; } /* prevent removal of non-empty dirs */ if ((target_ino->type == NF4DIR) && !dir_is_empty(txn, target_ino)) { status = NFS4ERR_NOTEMPTY; goto out_abort; } /* remove target inode from directory */ rc = fsdb_dirent_del(&srv.fsdb, txn, dir_ino->inum, &target, 0); if (rc) { status = NFS4ERR_IO; goto out_abort; } /* record directory change info */ cinfo.before = dir_ino->version; rc = inode_touch(txn, dir_ino); if (rc) { status = NFS4ERR_IO; goto out_abort; } cinfo.after = dir_ino->version; /* remove link, possibly deleting inode */ rc = inode_unlink(txn, target_ino); if (rc) { status = NFS4ERR_IO; goto out_abort; } rc = txn->commit(txn, 0); if (rc) { dbenv->err(dbenv, rc, "DB_ENV->txn_commit"); status = NFS4ERR_IO; goto out; } out: WR32(status); if (status == NFS4_OK) { WR32(cinfo.atomic ? 1 : 0); /* cinfo.atomic */ WR64(cinfo.before); /* cinfo.before */ WR64(cinfo.after); /* cinfo.after */ } inode_free(dir_ino); inode_free(target_ino); return status; out_abort: if (txn->abort(txn)) dbenv->err(dbenv, rc, "DB_ENV->txn_abort"); goto out; }
nfsstat4 nfs_op_link(struct nfs_cxn *cxn, const LINK4args *args, struct list_head *writes, struct rpc_write **wr) { nfsstat4 status; struct nfs_inode *dir_ino = NULL, *src_ino = NULL; struct nfs_buf newname; uint64_t before = 0, after = 0; DB_TXN *txn; DB_ENV *dbenv = srv.fsdb.env; int rc; newname.len = args->newname.utf8string_len; newname.val = args->newname.utf8string_val; if (debugging) applog(LOG_INFO, "op LINK (%.*s)", newname.len, newname.val); /* verify input parameters */ if (!valid_fh(cxn->current_fh) || !valid_fh(cxn->save_fh)) { status = NFS4ERR_NOFILEHANDLE; goto out; } if (newname.len > SRV_MAX_NAME) { status = NFS4ERR_NAMETOOLONG; goto out; } /* open transaction */ rc = dbenv->txn_begin(dbenv, NULL, &txn, 0); if (rc) { status = NFS4ERR_IO; dbenv->err(dbenv, rc, "DB_ENV->txn_begin"); goto out; } /* read source inode's directory inode */ dir_ino = inode_fhdec(txn, cxn->current_fh, 0); if (!dir_ino) { status = NFS4ERR_NOFILEHANDLE; goto out_abort; } /* make sure target is a directory */ if (dir_ino->type != NF4DIR) { status = NFS4ERR_NOTDIR; goto out_abort; } /* read source inode */ src_ino = inode_fhdec(txn, cxn->save_fh, 0); if (!src_ino) { status = NFS4ERR_NOFILEHANDLE; goto out_abort; } /* make sure source is a not a directory */ if (src_ino->type == NF4DIR) { status = NFS4ERR_ISDIR; goto out_abort; } before = dir_ino->version; /* add directory entry */ status = dir_add(txn, dir_ino, &newname, src_ino); if (status != NFS4_OK) goto out_abort; after = dir_ino->version; /* update source inode */ src_ino->n_link++; if (inode_touch(txn, src_ino)) { status = NFS4ERR_IO; goto out_abort; } /* close transaction */ rc = txn->commit(txn, 0); if (rc) { dbenv->err(dbenv, rc, "DB_ENV->txn_commit"); status = NFS4ERR_IO; goto out; } out: WR32(status); if (status == NFS4_OK) { WR32(1); /* cinfo.atomic */ WR64(before); /* cinfo.before */ WR64(after); /* cinfo.after */ } inode_free(src_ino); inode_free(dir_ino); return status; out_abort: if (txn->abort(txn)) dbenv->err(dbenv, rc, "DB_ENV->txn_abort"); goto out; }
/** * @brief Searches for a directory entry. * * @details Searches for a directory entry named @p filename in the directory * pointed to be @p dip. If @p create is not zero and such file entry * does not exist, the entry is created. * * @param dip Directory where the directory entry shall be searched. * @param filename Name of the directory entry that shall be searched. * @param buf Buffer where the directory entry is loaded. * @param create Create directory entry? * * @returns Upon successful completion, the directory entry is returned. In this * case, @p buf is set to point to the (locked) buffer associated to * the requested directory entry. However, upon failure, a #NULL * pointer is returned instead. * * @note @p dip must be locked. * @note @p filename must point to a valid location. * @note @p buf must point to a valid location */ PRIVATE struct d_dirent *dirent_search (struct inode *dip, const char *filename, struct buffer **buf, int create) { int i; /* Working directory entry index. */ int entry; /* Index of first free directory entry. */ block_t blk; /* Working block number. */ int nentries; /* Number of directory entries. */ struct d_dirent *d; /* Directory entry. */ nentries = dip->size/sizeof(struct d_dirent); /* Search from very first block. */ i = 0; entry = -1; blk = dip->blocks[0]; (*buf) = NULL; d = NULL; /* Search directory entry. */ while (i < nentries) { /* * Skip invalid blocks. As directory entries * are removed from a directory, a whole block * may become free. */ if (blk == BLOCK_NULL) { i += BLOCK_SIZE/sizeof(struct d_dirent); blk = block_map(dip, i*sizeof(struct d_dirent), 0); continue; } /* Get buffer. */ if ((*buf) == NULL) { (*buf) = bread(dip->dev, blk); d = (*buf)->data; } /* Get next block */ else if ((char *)d >= BLOCK_SIZE + (char *) (*buf)->data) { brelse((*buf)); (*buf) = NULL; blk = block_map(dip, i*sizeof(struct d_dirent), 0); continue; } /* Valid entry. */ if (d->d_ino != INODE_NULL) { /* Found */ if (!kstrncmp(d->d_name, filename, NAME_MAX)) { /* Duplicated entry. */ if (create) { brelse((*buf)); d = NULL; curr_proc->errno = EEXIST; } return (d); } } /* Remember entry index. */ else entry = i; d++; i++; } /* House keeping. */ if ((*buf) != NULL) { brelse((*buf)); (*buf) = NULL; } /* Create entry. */ if (create) { /* Expand directory. */ if (entry < 0) { entry = nentries; blk = block_map(dip, entry*sizeof(struct d_dirent), 1); /* Failed to create entry. */ if (blk == BLOCK_NULL) { curr_proc->errno = -ENOSPC; return (NULL); } dip->size += sizeof(struct d_dirent); inode_touch(dip); } else blk = block_map(dip, entry*sizeof(struct d_dirent), 0); (*buf) = bread(dip->dev, blk); entry %= (BLOCK_SIZE/sizeof(struct d_dirent)); d = &((struct d_dirent *)((*buf)->data))[entry]; return (d); } return (NULL); }