diff --git a/cmd/radFS/main.go b/cmd/radFS/main.go old mode 100644 new mode 100755 index 02cfc6f..76abfbb --- a/cmd/radFS/main.go +++ b/cmd/radFS/main.go @@ -37,7 +37,8 @@ func main() { } //c is a fuse connection to dev/fuse - c, err := fuse.Mount(cfg.mount) + //allowother() allows any user to access the point moint -> used in chown + c, err := fuse.Mount(cfg.mount, fuse.AllowOther()) if err != nil { log.Println(err) diff --git a/cmd/radFS/temp/a.txt b/cmd/radFS/temp/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/internal/fs/dir.go b/internal/fs/dir.go index 028e2f5..e9cfdaf 100644 --- a/internal/fs/dir.go +++ b/internal/fs/dir.go @@ -24,6 +24,8 @@ func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Atime = d.atime a.Mtime = d.mtime a.Ctime = d.ctime + a.Uid = d.uid + a.Gid = d.gid return nil } @@ -32,6 +34,17 @@ func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse. d.mu.Lock() defer d.mu.Unlock() + if req.Valid.Uid() { + + //if caller is not root and caller is trying to chown to uid that is not itself + if req.Header.Uid != 0 && req.Uid != req.Header.Uid { // to get the uid of the process making the req -> checking the caller + return syscall.EPERM + } + + d.uid = req.Uid + d.ctime = time.Now() + } + if req.Valid.Atime() { d.atime = req.Atime } @@ -46,6 +59,8 @@ func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse. resp.Attr.Atime = d.atime resp.Attr.Mtime = d.mtime resp.Attr.Ctime = d.ctime + resp.Attr.Uid = d.uid + resp.Attr.Gid = d.gid return nil @@ -117,6 +132,8 @@ func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error atime: time.Now(), ctime: time.Now(), mtime: time.Now(), + uid: req.Uid, + gid: req.Gid, } d.Nodes[req.Name] = newDir @@ -141,12 +158,15 @@ func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.Cr defer d.mu.Unlock() f := &File{ - inode: nextInode(), - data: []byte{}, - mode: uint32(req.Mode), - atime: time.Now(), - ctime: time.Now(), - mtime: time.Now(), + inode: nextInode(), + blocks: [][]byte{}, + size: 0, + mode: uint32(req.Mode), + atime: time.Now(), + ctime: time.Now(), + mtime: time.Now(), + uid: req.Uid, + gid: req.Gid, } if _, exists := d.Nodes[req.Name]; exists { // checking for dupes diff --git a/internal/fs/file.go b/internal/fs/file.go index 8e86778..e6b9092 100644 --- a/internal/fs/file.go +++ b/internal/fs/file.go @@ -4,17 +4,20 @@ import ( "context" "os" "time" + "syscall" "bazil.org/fuse" "bazil.org/fuse/fs" ) +const blockSize = 4096 + func (f *File) Attr(ctx context.Context, a *fuse.Attr) error { f.mu.Lock() defer f.mu.Unlock() a.Inode = f.inode a.Mode = os.FileMode(f.mode) - a.Size = uint64(len(f.data)) + a.Size = uint64(f.size) //using size instead of len because its [][]byte a.Atime = f.atime a.Mtime = f.mtime a.Ctime = f.ctime @@ -32,15 +35,35 @@ func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadR f.mu.Lock() defer f.mu.Unlock() - if req.Offset >= int64(len(f.data)) { + // checking if reading is starting from offset that actually exists + if req.Offset >= int64(f.size) { resp.Data = []byte{} - return nil } + // so that we dont read past file size end := req.Offset + int64(req.Size) - end = min(end, int64(len(f.data))) - resp.Data = f.data[req.Offset:end] + if end > int64(f.size) { + end = int64(f.size) + } + + var result []byte // final fully read file as a single array that we will reeturn + offset := req.Offset + + for offset < end { + blockIndex := offset / blockSize + blockOffset := offset % blockSize + + // min of how much space is left on currect block and how much data is left to read -> basically safety guard + toRead := min(int64(blockSize)-blockOffset, end-offset) + + result = append(result, f.blocks[blockIndex][blockOffset:blockOffset+toRead]...) //expanding each byte individually + + offset += toRead + } + + resp.Data = result + f.atime = time.Now() f.atime = time.Now() @@ -52,17 +75,39 @@ func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.Wri f.mu.Lock() defer f.mu.Unlock() - end := req.Offset + int64(len(req.Data)) + offset := req.Offset + data := req.Data + end := req.Offset + int64(len(req.Data)) // till where we'll be writing + + // grow blocks if needed + for end > int64(len(f.blocks))*blockSize { + f.blocks = append(f.blocks, make([]byte, blockSize)) + } - // Grow the buffer if needed - if end > int64(len(f.data)) { - newData := make([]byte, end) - copy(newData, f.data) - f.data = newData + // updating file size + if end > int64(f.size) { + f.size = uint64(end) } - copy(f.data[req.Offset:], req.Data) - resp.Size = len(req.Data) + written := 0 + + for len(data) > 0 { + blockIndex := offset / blockSize + blockOffset := offset % blockSize + + toWrite := min(int64(blockSize)-blockOffset, int64(len(data))) + + n := copy(f.blocks[blockIndex][blockOffset:blockOffset+toWrite], data[:toWrite]) + + data = data[n:] + offset += int64(n) + written += n + + } + + resp.Size = written + f.mtime = time.Now() + f.ctime = time.Now() // writing to file constitutes changes in certain fields of inode too f.mtime = time.Now() f.ctime = time.Now() // writing to file constitutes changes in certain fields of inode too @@ -80,17 +125,48 @@ func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse f.ctime = time.Now() } - if req.Valid.Size() { - if req.Size < uint64(len(f.data)) { - f.data = f.data[:req.Size] - f.ctime = time.Now() // cuz creating file here - } else { - newData := make([]byte, req.Size) - copy(newData, f.data) - f.data = newData + if req.Valid.Uid() { + + //if caller is not root and caller is trying to chown to uid that is not itself + if req.Header.Uid != 0 && req.Uid != req.Header.Uid{ // to get the uid of the process making the req -> checking the caller + return syscall.EPERM + } + + f.uid = req.Uid + f.ctime = time.Now() + } + + if req.Valid.Gid() { + f.gid = req.Gid + f.ctime = time.Now() + } + + if req.Valid.Size() { // mainly for truncate? + + newSize := req.Size + needed := (newSize + uint64(blockSize) - 1) / uint64(blockSize) // ceil division to find how many blocks are required to store the new size + + if newSize < f.size { //shrinking operation + + //start from where we need to start + f.blocks = f.blocks[:needed] + + if newSize > 0 { + last_offset := newSize % uint64(blockSize) + if last_offset != 0 { + clear(f.blocks[needed-1][last_offset:]) // since we are shirnking/truncating we need to remove(clear) the stuff we dont need + } + } + } else if newSize > f.size { // explanding operation + for uint64(len(f.blocks)) < needed { + f.blocks = append(f.blocks, make([]byte, blockSize)) + } } + + f.size = newSize f.mtime = time.Now() f.ctime = time.Now() + } if req.Valid.Atime() { @@ -102,10 +178,12 @@ func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse resp.Attr.Inode = f.inode resp.Attr.Mode = os.FileMode(f.mode) - resp.Attr.Size = uint64(len(f.data)) + resp.Attr.Size = f.size resp.Attr.Atime = f.atime resp.Attr.Mtime = f.mtime resp.Attr.Ctime = f.ctime + resp.Attr.Uid = f.uid + resp.Attr.Gid = f.gid return nil } diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 9fb9eac..52a65b7 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -24,37 +24,54 @@ func nextInode() uint64 { } func (f *FS) Root() (fs.Node, error) { - return &Dir{ + + content := []byte("Hello from radFS!\n") + blocks := [][]byte{} + for i := 0; i < len(content); i += blockSize { + end := min(i+blockSize, len(content)) + block := make([]byte, blockSize) + copy(block, content[i:end]) + blocks = append(blocks, block) + } + + root := &Dir{ inode: 1, Nodes: map[string]fs.Node{ + "hello.txt": &File{ - inode: nextInode(), - data: []byte("Hello from radFS!\n"), - mode: 0o666, - atime: time.Now(), - mtime: time.Now(), - ctime: time.Now(), + inode: nextInode(), + blocks: blocks, + size: uint64(len(content)), + mode: 0o666, + atime: time.Now(), + mtime: time.Now(), + ctime: time.Now(), + uid: uint32(os.Getuid()), + gid: uint32(os.Getgid()), }, }, fs: f, atime: time.Now(), mtime: time.Now(), ctime: time.Now(), + uid: uint32(os.Getuid()), + gid: uint32(os.Getgid()), } return root, nil } type File struct { - mu sync.Mutex - inode uint64 - data []byte - mode uint32 - atime time.Time // read - mtime time.Time // write | truncate - ctime time.Time // metadata (setattr) - uid uint32 - gid uint32 + mu sync.Mutex + inode uint64 + blocks [][]byte + size uint64 + mode uint32 + atime time.Time // read + mtime time.Time // write | truncate + ctime time.Time // metadata (setattr) + uid uint32 + gid uint32 } type Dir struct { @@ -65,4 +82,6 @@ type Dir struct { atime time.Time mtime time.Time ctime time.Time + uid uint32 + gid uint32 }