Skip to content

Commit 2254522

Browse files
committed
add DLM_LOCK opcode for fuse and add management of requested dlm locked areas
1 parent 8ab5cb4 commit 2254522

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

fs/fuse/file.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
#include "fuse_i.h"
1010

11+
#include "linux/list.h"
12+
#include "linux/printk.h"
13+
#include "linux/spinlock.h"
1114
#include <linux/pagemap.h>
1215
#include <linux/slab.h>
1316
#include <linux/kernel.h>
@@ -1395,6 +1398,149 @@ static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive)
13951398
}
13961399
}
13971400

1401+
static bool fuse_dlm_locked(struct file* file, loff_t offset, size_t length)
1402+
{
1403+
struct inode *inode = file_inode(file);
1404+
struct fuse_conn *fc = get_fuse_conn(inode);
1405+
struct fuse_inode *fi = get_fuse_inode(inode);
1406+
struct dlm_locked_area *area;
1407+
1408+
/* if dlm is not supported by fuse server, don't bother */
1409+
if (fc->no_dlm)
1410+
return true;
1411+
1412+
/* check the locked areas for the given offset and length */
1413+
list_for_each_entry(area, &fi->dlm_locked_areas, list) {
1414+
loff_t current_area_start_offset = area->offset;
1415+
loff_t current_area_end_offset = area->offset + area->size;
1416+
loff_t lock_end_offset = offset + length;
1417+
loff_t lock_start_offset = offset;
1418+
1419+
/* check if the locked areas are completely distinct, then we should continue */
1420+
if (current_area_end_offset < lock_start_offset
1421+
|| current_area_start_offset > lock_end_offset)
1422+
continue;
1423+
1424+
/* check if the given offset and length completely overlaps with the current area */
1425+
if (current_area_start_offset <= lock_start_offset
1426+
&& current_area_end_offset >= lock_end_offset) {
1427+
return true;
1428+
}
1429+
1430+
/* lock area has segment after the current area */
1431+
if(current_area_start_offset < lock_start_offset
1432+
&& current_area_end_offset > lock_start_offset
1433+
&& current_area_end_offset < lock_end_offset) {
1434+
offset = current_area_end_offset;
1435+
length = lock_end_offset - current_area_end_offset;
1436+
/* check all other areas for the part at the end of the locked area */
1437+
return fuse_dlm_locked(file, offset, length);
1438+
}
1439+
1440+
/* lock area has segment before the current area */
1441+
if (lock_start_offset < current_area_start_offset
1442+
&& lock_end_offset > current_area_start_offset
1443+
&& lock_end_offset < current_area_end_offset) {
1444+
offset = lock_start_offset;
1445+
length = current_area_start_offset - lock_start_offset;
1446+
/* check all other areas for the rest of the part */
1447+
return fuse_dlm_locked(file, offset, length);
1448+
}
1449+
1450+
/* If the lock area is larger than the current area, continue, some other areas might match partially
1451+
* If they don't we return false and the bigger chunk will be locked and merged with the partially matching one anyway.
1452+
* This is a case the fuse server has to be able to handle.
1453+
*/
1454+
}
1455+
return false;
1456+
}
1457+
1458+
/**
1459+
* check if the given offset and length extends the already locked area or we have to create a new area
1460+
*/
1461+
static void check_and_add_locked_area(struct fuse_inode *fi, loff_t offset, size_t length) {
1462+
struct dlm_locked_area *area;
1463+
1464+
spin_lock(&fi->lock);
1465+
/* iterate through the areas */
1466+
list_for_each_entry(area, &fi->dlm_locked_areas, list) {
1467+
loff_t current_area_start_offset = area->offset;
1468+
loff_t current_area_end_offset = area->offset + area->size;
1469+
loff_t lock_end_offset = offset + length;
1470+
loff_t lock_start_offset = offset;
1471+
1472+
/* if we have overlap, extend the locked area */
1473+
if (lock_start_offset >= current_area_start_offset && lock_start_offset <= current_area_end_offset) {
1474+
area->offset = MIN(current_area_start_offset, lock_start_offset);
1475+
area->size = MAX(current_area_end_offset, lock_end_offset) - area->offset;
1476+
spin_unlock(&fi->lock);
1477+
return;
1478+
}
1479+
}
1480+
1481+
/* create a new locked area */
1482+
area = kmalloc(sizeof(struct dlm_locked_area), GFP_KERNEL);
1483+
area->offset = offset;
1484+
area->size = length;
1485+
list_add_tail(&area->list, &fi->dlm_locked_areas);
1486+
spin_unlock(&fi->lock);
1487+
}
1488+
1489+
/**
1490+
* request a dlm lock from the fuse server
1491+
*/
1492+
static void fuse_get_dlm_write_lock(struct file *file, loff_t offset, size_t length)
1493+
{
1494+
struct fuse_file *ff = file->private_data;
1495+
struct inode *inode = file_inode(file);
1496+
struct fuse_conn *fc = get_fuse_conn(inode);
1497+
struct fuse_inode *fi = get_fuse_inode(inode);
1498+
struct fuse_mount *fm = ff->fm;
1499+
1500+
FUSE_ARGS(args);
1501+
struct fuse_dlm_lock_in inarg;
1502+
struct fuse_dlm_lock_out outarg;
1503+
int err;
1504+
1505+
/* note that the offset and length don't have to be page aligned here
1506+
but since we only get here on writeback caching we will send out
1507+
page aligned requests */
1508+
offset &= PAGE_MASK;
1509+
length = PAGE_ALIGN(offset + length) - offset;
1510+
1511+
if (fuse_dlm_locked(file, offset, length))
1512+
return; /* we already have this area locked */
1513+
1514+
memset(&inarg, 0, sizeof(inarg));
1515+
inarg.fh = ff->fh;
1516+
1517+
inarg.offset = offset;
1518+
inarg.size = length;
1519+
inarg.type = FUSE_DLM_LOCK_WRITE;
1520+
1521+
args.opcode = FUSE_DLM_LOCK;
1522+
args.nodeid = get_node_id(inode);
1523+
args.in_numargs = 1;
1524+
args.in_args[0].size = sizeof(inarg);
1525+
args.in_args[0].value = &inarg;
1526+
args.out_numargs = 1;
1527+
args.out_args[0].size = sizeof(outarg);
1528+
args.out_args[0].value = &outarg;
1529+
err = fuse_simple_request(fm, &args);
1530+
if (err == -ENOSYS) {
1531+
/* fuse server told us it does not support dlm, save the info */
1532+
fc->no_dlm = 1;
1533+
}
1534+
1535+
if (err)
1536+
return;
1537+
1538+
/* add the lock to the list of locked areas */
1539+
if (outarg.locksize) {
1540+
check_and_add_locked_area(fi, offset, outarg.locksize);
1541+
}
1542+
}
1543+
13981544
static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from)
13991545
{
14001546
struct file *file = iocb->ki_filp;
@@ -1403,6 +1549,8 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from)
14031549
struct inode *inode = mapping->host;
14041550
ssize_t err;
14051551
struct fuse_conn *fc = get_fuse_conn(inode);
1552+
loff_t pos = iocb->ki_pos;
1553+
size_t length = iov_iter_count(from);
14061554

14071555
if (fc->writeback_cache) {
14081556
/* Update size (EOF optimization) and mode (SUID clearing) */
@@ -1417,6 +1565,11 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from)
14171565
goto writethrough;
14181566
}
14191567

1568+
/* if we have dlm support acquire the lock for the area
1569+
* we are writing into */
1570+
if (!fc->no_dlm)
1571+
fuse_get_dlm_write_lock(file, pos, length);
1572+
14201573
return generic_file_write_iter(iocb, from);
14211574
}
14221575

@@ -3325,6 +3478,7 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags)
33253478

33263479
INIT_LIST_HEAD(&fi->write_files);
33273480
INIT_LIST_HEAD(&fi->queued_writes);
3481+
INIT_LIST_HEAD(&fi->dlm_locked_areas);
33283482
fi->writectr = 0;
33293483
fi->iocachectr = 0;
33303484
init_waitqueue_head(&fi->page_waitq);

fs/fuse/fuse_i.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ struct fuse_submount_lookup {
8484
struct fuse_forget_link *forget;
8585
};
8686

87+
/**
88+
* data structure to save the information that we have
89+
* requested dlm locks for the given area from the fuse server
90+
*/
91+
struct dlm_locked_area
92+
{
93+
struct list_head list;
94+
loff_t offset;
95+
size_t size;
96+
};
97+
8798
/** FUSE inode */
8899
struct fuse_inode {
89100
/** Inode data */
@@ -142,6 +153,9 @@ struct fuse_inode {
142153

143154
/* List of writepage requestst (pending or sent) */
144155
struct rb_root writepages;
156+
157+
/** List of dlm locked areas we have sent lock requests for */
158+
struct list_head dlm_locked_areas;
145159
};
146160

147161
/* readdir cache (directory only) */
@@ -835,6 +849,9 @@ struct fuse_conn {
835849
/* Is statx not implemented by fs? */
836850
unsigned int no_statx:1;
837851

852+
/* do we have support for dlm in the fs? */
853+
unsigned int no_dlm:1;
854+
838855
/* Use io_uring for communication */
839856
unsigned int io_uring;
840857

fs/fuse/fuse_trace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
EM( FUSE_SYNCFS, "FUSE_SYNCFS") \
5959
EM( FUSE_TMPFILE, "FUSE_TMPFILE") \
6060
EM( FUSE_STATX, "FUSE_STATX") \
61+
EM( FUSE_DLM_LOCK, "FUSE_DLM_LOCK") \
6162
EMe(CUSE_INIT, "CUSE_INIT")
6263

6364
/*

fs/fuse/inode.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "fuse_i.h"
1010
#include "dev_uring_i.h"
1111

12+
#include "linux/list.h"
1213
#include <linux/pagemap.h>
1314
#include <linux/slab.h>
1415
#include <linux/file.h>
@@ -146,6 +147,22 @@ static void fuse_cleanup_submount_lookup(struct fuse_conn *fc,
146147
kfree(sl);
147148
}
148149

150+
static void fuse_forget_all_dlm_lock_areas(struct fuse_inode *fi)
151+
{
152+
spin_lock(&fi->lock);
153+
/* forget about the dlm locks */
154+
if (!list_empty(&fi->dlm_locked_areas)) {
155+
struct dlm_locked_area *area, *tmp;
156+
list_for_each_entry_safe(area, tmp, &fi->dlm_locked_areas, list) {
157+
list_del(&area->list);
158+
printk("free area %lld size: %ld\n", area->offset, area->size);
159+
kfree(area);
160+
}
161+
}
162+
spin_unlock(&fi->lock);
163+
}
164+
165+
149166
static void fuse_evict_inode(struct inode *inode)
150167
{
151168
struct fuse_inode *fi = get_fuse_inode(inode);
@@ -182,6 +199,7 @@ static void fuse_evict_inode(struct inode *inode)
182199
if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) {
183200
WARN_ON(!list_empty(&fi->write_files));
184201
WARN_ON(!list_empty(&fi->queued_writes));
202+
fuse_forget_all_dlm_lock_areas(fi);
185203
}
186204
}
187205

@@ -559,6 +577,7 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
559577

560578
fuse_invalidate_attr(inode);
561579
forget_all_cached_acls(inode);
580+
fuse_forget_all_dlm_lock_areas(fi);
562581
if (offset >= 0) {
563582
pg_start = offset >> PAGE_SHIFT;
564583
if (len <= 0)

include/uapi/linux/fuse.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@
220220
* - FUSE_URING_IN_OUT_HEADER_SZ
221221
* - FUSE_URING_OP_IN_OUT_SZ
222222
* - enum fuse_uring_cmd
223+
* 7.43
224+
* - add FUSE_DLM_LOCK
223225
*/
224226

225227
#ifndef _LINUX_FUSE_H
@@ -632,6 +634,7 @@ enum fuse_opcode {
632634
FUSE_SYNCFS = 50,
633635
FUSE_TMPFILE = 51,
634636
FUSE_STATX = 52,
637+
FUSE_DLM_LOCK = 53,
635638

636639
/* CUSE specific operations */
637640
CUSE_INIT = 4096,
@@ -1166,6 +1169,40 @@ struct fuse_supp_groups {
11661169
uint32_t groups[];
11671170
};
11681171

1172+
/**
1173+
* Type of the dlm lock requested
1174+
*/
1175+
enum fuse_dlm_lock_type {
1176+
FUSE_DLM_LOCK_NONE = 0,
1177+
FUSE_DLM_LOCK_READ = 1,
1178+
FUSE_DLM_LOCK_WRITE = 2
1179+
};
1180+
1181+
/**
1182+
* struct fuse_dlm_lock_in - Lock request
1183+
* @fh: file handle
1184+
* @offset: offset into the file
1185+
* @size: size of the locked region
1186+
* @type: type of lock
1187+
*/
1188+
struct fuse_dlm_lock_in {
1189+
uint64_t fh;
1190+
uint64_t offset;
1191+
uint32_t size;
1192+
uint32_t type;
1193+
};
1194+
1195+
/**
1196+
* struct fuse_dlm_lock_out - Lock response
1197+
* @locksize: how many bytes where locked by the call
1198+
* (most of the time we want to lock more than is requested
1199+
* to reduce number of calls)
1200+
*/
1201+
struct fuse_dlm_lock_out {
1202+
uint32_t locksize;
1203+
uint32_t padding;
1204+
};
1205+
11691206
/**
11701207
* Size of the ring buffer header
11711208
*/

0 commit comments

Comments
 (0)