+ subvol_inum inum = map_root_ino(ino);
+ struct bch_fs *c = fuse_req_userdata(req);
+
+ fuse_log(FUSE_LOG_DEBUG, "bcachefs_fuse_read(%llu, %zd, %lld)\n",
+ inum, size, offset);
+
+ /* Check inode size. */
+ struct bch_inode_unpacked bi;
+ int ret = bch2_inode_find_by_inum(c, inum, &bi);
+ if (ret) {
+ fuse_reply_err(req, -ret);
+ return;
+ }
+
+ off_t end = min_t(u64, bi.bi_size, offset + size);
+ if (end <= offset) {
+ fuse_reply_buf(req, NULL, 0);
+ return;
+ }
+ size = end - offset;
+
+ struct fuse_align_io align = align_io(c, size, offset);
+
+ void *buf = aligned_alloc(PAGE_SIZE, align.size);
+ if (!buf) {
+ fuse_reply_err(req, ENOMEM);
+ return;
+ }
+
+ ret = read_aligned(c, inum, align.size, align.start, buf);
+
+ if (likely(!ret))
+ fuse_reply_buf(req, buf + align.pad_start, size);
+ else
+ fuse_reply_err(req, -ret);
+
+ free(buf);
+}
+
+static int inode_update_times(struct bch_fs *c, subvol_inum inum)
+{
+ struct btree_trans *trans;
+ struct btree_iter iter;
+ struct bch_inode_unpacked inode_u;
+ int ret = 0;
+ u64 now;
+
+ trans = bch2_trans_get(c);
+retry:
+ bch2_trans_begin(trans);
+ now = bch2_current_time(c);
+
+ ret = bch2_inode_peek(trans, &iter, &inode_u, inum, BTREE_ITER_INTENT);
+ if (ret)
+ goto err;
+
+ inode_u.bi_mtime = now;
+ inode_u.bi_ctime = now;
+
+ ret = bch2_inode_write(trans, &iter, &inode_u);
+ if (ret)
+ goto err;
+
+ ret = bch2_trans_commit(trans, NULL, NULL,
+ BCH_TRANS_COMMIT_no_enospc);
+err:
+ bch2_trans_iter_exit(trans, &iter);
+ if (ret == -EINTR)
+ goto retry;
+
+ bch2_trans_put(trans);
+ return ret;
+}
+
+static int write_aligned(struct bch_fs *c, subvol_inum inum,
+ struct bch_io_opts io_opts, void *buf,
+ size_t aligned_size, off_t aligned_offset,
+ off_t new_i_size, size_t *written_out)
+{
+
+ struct write_aligned_op_t w = { 0 }
+;
+ struct bch_write_op *op = &w.op;
+ struct bio_vec bv;
+
+ BUG_ON(aligned_size & (block_bytes(c) - 1));
+ BUG_ON(aligned_offset & (block_bytes(c) - 1));
+
+ *written_out = 0;
+
+ closure_init_stack(&w.cl);
+
+ bch2_write_op_init(op, c, io_opts); /* XXX reads from op?! */
+ op->write_point = writepoint_hashed(0);
+ op->nr_replicas = io_opts.data_replicas;
+ op->target = io_opts.foreground_target;
+ op->subvol = inum.subvol;
+ op->pos = POS(inum.inum, aligned_offset >> 9);
+ op->new_i_size = new_i_size;
+ op->end_io = bcachefs_fuse_write_endio;
+
+ userbio_init(&op->wbio.bio, &bv, buf, aligned_size);
+ bio_set_op_attrs(&op->wbio.bio, REQ_OP_WRITE, REQ_SYNC);
+
+ if (bch2_disk_reservation_get(c, &op->res, aligned_size >> 9,
+ op->nr_replicas, 0)) {
+ /* XXX: use check_range_allocated like dio write path */
+ return -ENOSPC;
+ }
+
+ closure_get(&w.cl);
+
+ closure_call(&op->cl, bch2_write, NULL, NULL);
+
+ closure_sync(&w.cl);
+
+ if (!op->error)
+ *written_out = op->written << 9;
+
+ return op->error;
+}
+
+static void bcachefs_fuse_write(fuse_req_t req, fuse_ino_t ino,
+ const char *buf, size_t size,
+ off_t offset,
+ struct fuse_file_info *fi)
+{
+ subvol_inum inum = map_root_ino(ino);
+ struct bch_fs *c = fuse_req_userdata(req);
+ struct bch_io_opts io_opts;
+ size_t aligned_written;
+ int ret = 0;
+
+ fuse_log(FUSE_LOG_DEBUG, "bcachefs_fuse_write(%llu, %zd, %lld)\n",
+ inum, size, offset);
+
+ struct fuse_align_io align = align_io(c, size, offset);
+ void *aligned_buf = aligned_alloc(PAGE_SIZE, align.size);
+ BUG_ON(!aligned_buf);
+
+ if (get_inode_io_opts(c, inum, &io_opts)) {
+ ret = -ENOENT;
+ goto err;
+ }
+
+ /* Realign the data and read in start and end, if needed */
+
+ /* Read partial start data. */
+ if (align.pad_start) {
+ memset(aligned_buf, 0, block_bytes(c));
+
+ ret = read_aligned(c, inum, block_bytes(c), align.start,
+ aligned_buf);
+ if (ret)
+ goto err;
+ }
+
+ /*
+ * Read partial end data. If the whole write fits in one block, the
+ * start data and the end data are the same so this isn't needed.
+ */
+ if (align.pad_end &&
+ !(align.pad_start && align.size == block_bytes(c))) {
+ off_t partial_end_start = align.end - block_bytes(c);
+ size_t buf_offset = align.size - block_bytes(c);
+
+ memset(aligned_buf + buf_offset, 0, block_bytes(c));
+
+ ret = read_aligned(c, inum, block_bytes(c), partial_end_start,
+ aligned_buf + buf_offset);
+ if (ret)
+ goto err;
+ }
+
+ /* Overlay what we want to write. */
+ memcpy(aligned_buf + align.pad_start, buf, size);
+
+ /* Actually write. */
+ ret = write_aligned(c, inum, io_opts, aligned_buf,
+ align.size, align.start,
+ offset + size, &aligned_written);
+
+ /* Figure out how many unaligned bytes were written. */
+ size_t written = align_fix_up_bytes(&align, aligned_written);
+ BUG_ON(written > size);
+
+ fuse_log(FUSE_LOG_DEBUG, "bcachefs_fuse_write: wrote %zd bytes\n",
+ written);
+
+ if (written > 0)
+ ret = 0;
+
+ /*
+ * Update inode times.
+ * TODO: Integrate with bch2_extent_update()
+ */
+ if (!ret)
+ ret = inode_update_times(c, inum);
+
+ if (!ret) {
+ BUG_ON(written == 0);
+ fuse_reply_write(req, written);
+ free(aligned_buf);
+ return;
+ }
+
+err:
+ fuse_reply_err(req, -ret);
+ free(aligned_buf);
+}
+
+static void bcachefs_fuse_symlink(fuse_req_t req, const char *link,
+ fuse_ino_t dir_ino, const char *name)
+{
+ subvol_inum dir = map_root_ino(dir_ino);