From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-oi1-f170.google.com (mail-oi1-f170.google.com [209.85.167.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B8F19367B73 for ; Fri, 29 May 2026 03:19:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780024765; cv=none; b=PluDNTB+/XlfDef9tVwuLPmtZQYsQWQGOPs+ksCimjOeg/UbO4oScF9vSKHN4WHMf6yP/JnRxTJpImimmQgX04GaG3lvcFtU5tVT94yvrJTiiYCimpxAbWLaLQ6tpaflkCZJejZ0PLK6sRNvfCfa+4XfTtpjgISzkrFU/KELmS0= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780024765; c=relaxed/simple; bh=40T2GLBqj4FpYx2f+Q5O8Te1Fo7OcJyPDzJ1RlcCGmM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=io3CQC4mLpJtedRFSMdleraaAVK9F7i5rN5g/lMaQiYuYixMRgonKEuC4sHWFaqz3P1mAiranXmvIvkV9qbdmLso/Sy51iwN029sbLd/nCRpdgI6HIuvOlS7nu3Z5/Ki7SyGeBN83kDRh4WiQgOaWWrUdUNBcAh2SbTgSyshARc= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=V99MNcJx; arc=none smtp.client-ip=209.85.167.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="V99MNcJx" Received: by mail-oi1-f170.google.com with SMTP id 5614622812f47-485433a6889so2346739b6e.0 for ; Thu, 28 May 2026 20:19:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780024763; x=1780629563; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=o9Db9daSum/vcEWTe0jIogWuT23gsSW5PUBP5KH0NAU=; b=V99MNcJxDllHIiIlVvlfYNZaYIxV/vgbwNdLncReVf2RD5vEgR6Zhl8a1k61Buv2xM 7Ns2p2a7fglX4/jVzyS4RUvab+46F+gpImkFUDDETE/e1o5cDz+BkR9OPHWgaUk+sgh2 sb8P4b5BUW1wWYvEmHcu7R5gHOn8/pwohfdVyw4DzJ87hzQkujqct9Me1m3hN3k7pgFY Czeg9Mvl0jSLiXkWPgWDZ5gKWjZHp/b5du7Y5+djc1p9fv7+XEvBtdH0fSIDCkG16Y3l 8My/Lk38kgrO9gvhYnKYr0U85/Ek2J8zQZF/lqBg5wjqgbiAqe4+wfO+XrqAe2W2DVpv uihQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780024763; x=1780629563; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=o9Db9daSum/vcEWTe0jIogWuT23gsSW5PUBP5KH0NAU=; b=gpATBovtA5k3OEKjniLLFBmSWZzNlfF0HvlCR+uFJ2ioiKiI6Rw8Jg/62YguS+A/yC 6HLfZFYMg4wHHNDg/y4VjNpYmkRKXhGDYExdG0/cttN1pGhTKtvz+bX6u2MQ0S3WSa4d ZUJJ7zmREgdshMXZxyYM81VdatWkVmFc/NiMIc/tvsULsa2XtF5CPoRpCFCH1jV0yu+V 0lWTHCBTEPzkZr4YoxWRwgY9PWul5XxOUZ3tzUAVvqPnqS4+AUrUWBRbmXDX29IgCMal egmIUQUAHkEtpnjjRLAedPj/L+OBeLtt/MAYuKi1uqU81i1j4fp7UZfGEPtV17SMVudl WByA== X-Forwarded-Encrypted: i=1; AFNElJ+vLaWeT45ofG+c6af/Nxk1vtOVDMpsw9PlopcuxcigKFRjrJG4B5aw2bL8Zy5/DczOQNgOEXk=@vger.kernel.org X-Gm-Message-State: AOJu0YxobCZ2WrXOfnaAoc07QcqH4SMYIZwnk1iu+s0WQJ4sjNcJrwzM nNy23GJT3rQ+CU6eDfy9+nd+xmeCVYwBQaSRSocoyCYo5KDG5Y/s4CGc X-Gm-Gg: Acq92OGeIdr4BdTMUdgw3wYENdgFOx3bK48or87tvp4ycWD+4hrtEUWW9MFkjrMQ6It age0zI8HwApAdDcSfe80NpND/1BEx2dBdfkdWY1fABh3/mT+8ToH6e0Ok5dx+i1AsvAvcmrvd98 i/riUS0X6oMwc3WufotftqHF//ecEsCEEHNaEL4p7bv98zA9G+P/roO8opeazZmTlRFT1OfHek1 F2FyiGhUIALS8YrwKPb+TMJSnyPTXqDuK2NDsXDgzOA/66+8uRwTPJWBemYyIOstlkg/hOkj6g1 KA06RvZiEjFt/p4S0eaqaMvkmc0dOjIBuLMlcBqZFyb0NC6bZbU65uQXfYViXzZva1wwGrgSlzm OAfvh3ck8vveOS/ZLWkNRhGvy4gkYT7V7YLqVsHN5GjfiSHW3D2AnEr2FDs2Gd4pO5HF6tAcBJH mu02MORD5xqQDb141gB/AT5KbkEuopZVAoNbxnIADuCOJ64nRmJfBd/Gj1WKWKc3eXfaI3DYLHr mk35KL5goUjI67ZLGtC+1HBTutBwY3TPM2bZ24CA1H7qQms3k85yMJFlhX3r2DOPQ== X-Received: by 2002:a05:6808:3318:b0:485:467f:a307 with SMTP id 5614622812f47-485e6c9fd72mr901051b6e.42.1780024762736; Thu, 28 May 2026 20:19:22 -0700 (PDT) Received: from rdf-gcp2.us-central1-b.c.storage-xlrait-66065.internal (163.80.112.136.bc.googleusercontent.com. [136.112.80.163]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7e695bb393bsm556788a34.7.2026.05.28.20.19.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 20:19:22 -0700 (PDT) From: Russ Fellows To: linux-fsdevel@vger.kernel.org Cc: miklos@szeredi.hu, linux-kernel@vger.kernel.org, Russ Fellows , stable@vger.kernel.org Subject: [PATCH 1/2] fuse: fix FOPEN_PARALLEL_DIRECT_WRITES being ignored for passthrough writes Date: Fri, 29 May 2026 03:19:15 +0000 Message-ID: <20260529031918.7361-2-russ.fellows@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260529031918.7361-1-russ.fellows@gmail.com> References: <20260529031918.7361-1-russ.fellows@gmail.com> Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit FOPEN_PARALLEL_DIRECT_WRITES has no effect on passthrough-backed FUSE files due to two independent bugs that each prevent it from working. Both must be fixed to restore parallel write concurrency. Bug 1: fuse_passthrough_write_iter() acquires the exclusive inode lock directly: inode_lock(inode); ret = backing_file_write_iter(...); inode_unlock(inode); This serializes all concurrent writers regardless of whether the server set FOPEN_PARALLEL_DIRECT_WRITES. The flag is checked by fuse_dio_wr_exclusive_lock(), called from fuse_dio_lock(), called from fuse_direct_write_iter() -- the non-passthrough O_DIRECT path. fuse_file_write_iter() routes passthrough opens to fuse_passthrough_write_iter() instead, bypassing the flag check entirely. Bug 2: fuse_file_io_open() in iomode.c strips FOPEN_PARALLEL_DIRECT_WRITES from any open that lacks FOPEN_DIRECT_IO: if (!(ff->open_flags & FOPEN_DIRECT_IO)) ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES; This is correct for regular direct-IO opens where FOPEN_DIRECT_IO ensures O_DIRECT is actually in effect. It is wrong for passthrough opens: a passthrough file already bypasses the FUSE page cache by definition, so FOPEN_DIRECT_IO is redundant and should not be required to preserve the parallel-writes flag. Note: adding FOPEN_DIRECT_IO to the daemon's open flags is not a valid workaround. fuse_file_write_iter() checks FOPEN_DIRECT_IO before FOPEN_PASSTHROUGH, so setting both causes writes to be routed through fuse_direct_write_iter() (requiring a userspace round-trip) instead of fuse_passthrough_write_iter() (zero-copy kernel path). Combined effect: a daemon that opens with FOPEN_PASSTHROUGH | FOPEN_PARALLEL_DIRECT_WRITES (without FOPEN_DIRECT_IO) has the parallel flag stripped by Bug 2 before Bug 1 is even reached. Both bugs must be fixed together. Fix Bug 1: make fuse_dio_lock() and fuse_dio_unlock() non-static and call them from fuse_passthrough_write_iter(), replacing the open-coded inode_lock/inode_unlock. This reuses the existing logic that handles FOPEN_PARALLEL_DIRECT_WRITES, append writes, writes past EOF, and page-cache IO mode transitions. Fix Bug 2: skip the FOPEN_PARALLEL_DIRECT_WRITES strip when FOPEN_PASSTHROUGH is set. The flag remains stripped for non-passthrough opens without FOPEN_DIRECT_IO, preserving existing behaviour. Safety: backing_file_write_iter() calls into the backing filesystem's write_iter (e.g. xfs_file_write_iter), which acquires the backing inode's own lock independently. The FUSE inode lock and the backing inode lock are entirely separate; using inode_lock_shared on the FUSE inode does not affect the backing filesystem's concurrency control. Fixes: 4d99ff8f6b85 ("fuse: implement open/create with FOPEN_PASSTHROUGH") Cc: stable@vger.kernel.org Signed-off-by: Russ Fellows --- fs/fuse/file.c | 6 +++--- fs/fuse/fuse_i.h | 2 ++ fs/fuse/iomode.c | 8 ++++++-- fs/fuse/passthrough.c | 6 +++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index f94f3dc082c6..602c3f18676e 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1428,8 +1428,8 @@ static bool fuse_dio_wr_exclusive_lock(struct kiocb *iocb, struct iov_iter *from return false; } -static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, - bool *exclusive) +void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, + bool *exclusive) { struct inode *inode = file_inode(iocb->ki_filp); struct fuse_inode *fi = get_fuse_inode(inode); @@ -1455,7 +1455,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, } } -static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) +void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) { struct inode *inode = file_inode(iocb->ki_filp); struct fuse_inode *fi = get_fuse_inode(inode); @@ -1469,7 +1469,7 @@ static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) } } -static const struct iomap_write_ops fuse_iomap_write_ops = { +static const struct iomap_write_ops fuse_iomap_write_ops = { /* unchanged */ .read_folio_range = fuse_iomap_read_folio_range, }; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 17423d4e3cfa..120de517cea0 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1541,6 +1541,8 @@ int fuse_file_io_open(struct file *file, struct inode *inode); void fuse_file_io_release(struct fuse_file *ff, struct inode *inode); /* file.c */ +void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, bool *exclusive); +void fuse_dio_unlock(struct kiocb *iocb, bool exclusive); struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, unsigned int open_flags, bool isdir); void fuse_file_release(struct inode *inode, struct fuse_file *ff, diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c index c99e285f3..b3f51e3d1 100644 --- a/fs/fuse/iomode.c +++ b/fs/fuse/iomode.c @@ -214,10 +214,14 @@ int fuse_file_io_open(struct file *file, struct inode *inode) if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH)) goto fail; - /* - * FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO. - */ - if (!(ff->open_flags & FOPEN_DIRECT_IO)) + /* + * FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO, except for + * passthrough opens which bypass the page cache regardless and do not + * need FOPEN_DIRECT_IO to guarantee direct I/O semantics. + */ + if (!(ff->open_flags & FOPEN_DIRECT_IO) && + !(ff->open_flags & FOPEN_PASSTHROUGH)) ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES; /* diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c index f2d08ac2459b..f83d0a27cfb9 100644 --- a/fs/fuse/passthrough.c +++ b/fs/fuse/passthrough.c @@ -54,11 +54,11 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, struct iov_iter *iter) { struct file *file = iocb->ki_filp; - struct inode *inode = file_inode(file); struct fuse_file *ff = file->private_data; struct file *backing_file = fuse_file_passthrough(ff); size_t count = iov_iter_count(iter); ssize_t ret; + bool exclusive; struct backing_file_ctx ctx = { .cred = ff->cred, .end_write = fuse_passthrough_end_write, @@ -70,10 +70,10 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, if (!count) return 0; - inode_lock(inode); + fuse_dio_lock(iocb, iter, &exclusive); ret = backing_file_write_iter(backing_file, iter, iocb, iocb->ki_flags, &ctx); - inode_unlock(inode); + fuse_dio_unlock(iocb, exclusive); return ret; } -- 2.51.0