| /* |
| * linux/fs/hfs/file_cap.c |
| * |
| * Copyright (C) 1995-1997 Paul H. Hargrove |
| * This file may be distributed under the terms of the GNU General Public License. |
| * |
| * This file contains the file_ops and inode_ops for the metadata |
| * files under the CAP representation. |
| * |
| * The source code distribution of the Columbia AppleTalk Package for |
| * UNIX, version 6.0, (CAP) was used as a specification of the |
| * location and format of files used by CAP's Aufs. No code from CAP |
| * appears in hfs_fs. hfs_fs is not a work ``derived'' from CAP in |
| * the sense of intellectual property law. |
| * |
| * "XXX" in a comment is a note to myself to consider changing something. |
| * |
| * In function preconditions the term "valid" applied to a pointer to |
| * a structure means that the pointer is non-NULL and the structure it |
| * points to has all fields initialized to consistent values. |
| */ |
| |
| #include "hfs.h" |
| #include <linux/hfs_fs_sb.h> |
| #include <linux/hfs_fs_i.h> |
| #include <linux/hfs_fs.h> |
| #include <linux/smp_lock.h> |
| |
| /*================ Forward declarations ================*/ |
| static loff_t cap_info_llseek(struct file *, loff_t, |
| int); |
| static hfs_rwret_t cap_info_read(struct file *, char *, |
| hfs_rwarg_t, loff_t *); |
| static hfs_rwret_t cap_info_write(struct file *, const char *, |
| hfs_rwarg_t, loff_t *); |
| /*================ Function-like macros ================*/ |
| |
| /* |
| * OVERLAPS() |
| * |
| * Determines if a given range overlaps the specified structure member |
| */ |
| #define OVERLAPS(START, END, TYPE, MEMB) \ |
| ((END > offsetof(TYPE, MEMB)) && \ |
| (START < offsetof(TYPE, MEMB) + sizeof(((TYPE *)0)->MEMB))) |
| |
| /*================ Global variables ================*/ |
| |
| struct file_operations hfs_cap_info_operations = { |
| llseek: cap_info_llseek, |
| read: cap_info_read, |
| write: cap_info_write, |
| fsync: file_fsync, |
| }; |
| |
| struct inode_operations hfs_cap_info_inode_operations = { |
| setattr: hfs_notify_change_cap, |
| }; |
| |
| /*================ File-local functions ================*/ |
| |
| /* |
| * cap_build_meta() |
| * |
| * Build the metadata structure. |
| */ |
| static void cap_build_meta(struct hfs_cap_info *meta, |
| struct hfs_cat_entry *entry) |
| { |
| memset(meta, 0, sizeof(*meta)); |
| memcpy(meta->fi_fndr, &entry->info, 32); |
| if ((entry->type == HFS_CDR_FIL) && |
| (entry->u.file.flags & HFS_FIL_LOCK)) { |
| /* Couple the locked bit of the file to the |
| AFP {write,rename,delete} inhibit bits. */ |
| hfs_put_hs(HFS_AFP_RDONLY, meta->fi_attr); |
| } |
| meta->fi_magic1 = HFS_CAP_MAGIC1; |
| meta->fi_version = HFS_CAP_VERSION; |
| meta->fi_magic = HFS_CAP_MAGIC; |
| meta->fi_bitmap = HFS_CAP_LONGNAME; |
| memcpy(meta->fi_macfilename, entry->key.CName.Name, |
| entry->key.CName.Len); |
| meta->fi_datemagic = HFS_CAP_DMAGIC; |
| meta->fi_datevalid = HFS_CAP_MDATE | HFS_CAP_CDATE; |
| hfs_put_nl(hfs_m_to_htime(entry->create_date), meta->fi_ctime); |
| hfs_put_nl(hfs_m_to_htime(entry->modify_date), meta->fi_mtime); |
| hfs_put_nl(CURRENT_TIME, meta->fi_utime); |
| } |
| |
| static loff_t cap_info_llseek(struct file *file, loff_t offset, int origin) |
| { |
| long long retval; |
| |
| lock_kernel(); |
| |
| switch (origin) { |
| case 2: |
| offset += file->f_dentry->d_inode->i_size; |
| break; |
| case 1: |
| offset += file->f_pos; |
| } |
| retval = -EINVAL; |
| if (offset>=0 && offset<=HFS_FORK_MAX) { |
| if (offset != file->f_pos) { |
| file->f_pos = offset; |
| file->f_reada = 0; |
| } |
| retval = offset; |
| } |
| unlock_kernel(); |
| return retval; |
| } |
| |
| /* |
| * cap_info_read() |
| * |
| * This is the read() entry in the file_operations structure for CAP |
| * metadata files. The purpose is to transfer up to 'count' bytes |
| * from the file corresponding to 'inode' beginning at offset |
| * 'file->f_pos' to user-space at the address 'buf'. The return value |
| * is the number of bytes actually transferred. |
| */ |
| static hfs_rwret_t cap_info_read(struct file *filp, char *buf, |
| hfs_rwarg_t count, loff_t *ppos) |
| { |
| struct inode *inode = filp->f_dentry->d_inode; |
| struct hfs_cat_entry *entry = HFS_I(inode)->entry; |
| hfs_s32 left, size, read = 0; |
| hfs_u32 pos; |
| |
| if (!S_ISREG(inode->i_mode)) { |
| hfs_warn("hfs_cap_info_read: mode = %07o\n", inode->i_mode); |
| return -EINVAL; |
| } |
| |
| pos = *ppos; |
| if (pos > HFS_FORK_MAX) { |
| return 0; |
| } |
| size = inode->i_size; |
| if (pos > size) { |
| left = 0; |
| } else { |
| left = size - pos; |
| } |
| if (left > count) { |
| left = count; |
| } |
| if (left <= 0) { |
| return 0; |
| } |
| |
| if (pos < sizeof(struct hfs_cap_info)) { |
| int memcount = sizeof(struct hfs_cap_info) - pos; |
| struct hfs_cap_info meta; |
| |
| if (memcount > left) { |
| memcount = left; |
| } |
| cap_build_meta(&meta, entry); |
| memcount -= copy_to_user(buf, ((char *)&meta) + pos, memcount); |
| left -= memcount; |
| read += memcount; |
| pos += memcount; |
| buf += memcount; |
| } |
| |
| if (left > 0) { |
| clear_user(buf, left); |
| pos += left; |
| } |
| |
| if (read) { |
| inode->i_atime = CURRENT_TIME; |
| *ppos = pos; |
| mark_inode_dirty(inode); |
| } |
| |
| return read; |
| } |
| |
| /* |
| * cap_info_write() |
| * |
| * This is the write() entry in the file_operations structure for CAP |
| * metadata files. The purpose is to transfer up to 'count' bytes |
| * to the file corresponding to 'inode' beginning at offset |
| * '*ppos' from user-space at the address 'buf'. |
| * The return value is the number of bytes actually transferred. |
| */ |
| static hfs_rwret_t cap_info_write(struct file *filp, const char *buf, |
| hfs_rwarg_t count, loff_t *ppos) |
| { |
| struct inode *inode = filp->f_dentry->d_inode; |
| hfs_u32 pos; |
| |
| if (!S_ISREG(inode->i_mode)) { |
| hfs_warn("hfs_file_write: mode = %07o\n", inode->i_mode); |
| return -EINVAL; |
| } |
| if (count <= 0) { |
| return 0; |
| } |
| |
| pos = (filp->f_flags & O_APPEND) ? inode->i_size : *ppos; |
| |
| if (pos > HFS_FORK_MAX) { |
| return 0; |
| } |
| |
| *ppos += count; |
| if (*ppos > HFS_FORK_MAX) { |
| *ppos = HFS_FORK_MAX; |
| count = HFS_FORK_MAX - pos; |
| } |
| |
| if (*ppos > inode->i_size) |
| inode->i_size = *ppos; |
| |
| /* Only deal with the part we store in memory */ |
| if (pos < sizeof(struct hfs_cap_info)) { |
| int end, mem_count; |
| struct hfs_cat_entry *entry = HFS_I(inode)->entry; |
| struct hfs_cap_info meta; |
| |
| mem_count = sizeof(struct hfs_cap_info) - pos; |
| if (mem_count > count) { |
| mem_count = count; |
| } |
| end = pos + mem_count; |
| |
| cap_build_meta(&meta, entry); |
| mem_count -= copy_from_user(((char *)&meta) + pos, buf, mem_count); |
| |
| /* Update finder attributes if changed */ |
| if (OVERLAPS(pos, end, struct hfs_cap_info, fi_fndr)) { |
| memcpy(&entry->info, meta.fi_fndr, 32); |
| hfs_cat_mark_dirty(entry); |
| } |
| |
| /* Update file flags if changed */ |
| if (OVERLAPS(pos, end, struct hfs_cap_info, fi_attr) && |
| (entry->type == HFS_CDR_FIL)) { |
| int locked = hfs_get_ns(&meta.fi_attr) & |
| htons(HFS_AFP_WRI); |
| hfs_u8 new_flags; |
| |
| if (locked) { |
| new_flags = entry->u.file.flags | HFS_FIL_LOCK; |
| } else { |
| new_flags = entry->u.file.flags & ~HFS_FIL_LOCK; |
| } |
| |
| if (new_flags != entry->u.file.flags) { |
| entry->u.file.flags = new_flags; |
| hfs_cat_mark_dirty(entry); |
| hfs_file_fix_mode(entry); |
| } |
| } |
| |
| /* Update CrDat if changed */ |
| if (OVERLAPS(pos, end, struct hfs_cap_info, fi_ctime)) { |
| entry->create_date = |
| hfs_h_to_mtime(hfs_get_nl(meta.fi_ctime)); |
| hfs_cat_mark_dirty(entry); |
| } |
| |
| /* Update MdDat if changed */ |
| if (OVERLAPS(pos, end, struct hfs_cap_info, fi_mtime)) { |
| entry->modify_date = |
| hfs_h_to_mtime(hfs_get_nl(meta.fi_mtime)); |
| hfs_cat_mark_dirty(entry); |
| } |
| } |
| |
| inode->i_mtime = inode->i_ctime = CURRENT_TIME; |
| mark_inode_dirty(inode); |
| return count; |
| } |