1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * V9FS FID Management 4 * 5 * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net> 6 * Copyright (C) 2005, 2006 by Eric Van Hensbergen <ericvh@gmail.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/errno.h> 11 #include <linux/fs.h> 12 #include <linux/slab.h> 13 #include <linux/sched.h> 14 #include <net/9p/9p.h> 15 #include <net/9p/client.h> 16 17 #include "v9fs.h" 18 #include "v9fs_vfs.h" 19 #include "fid.h" 20 21 static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid) 22 { 23 hlist_add_head(&fid->dlist, (struct hlist_head *)&dentry->d_fsdata); 24 } 25 26 27 /** 28 * v9fs_fid_add - add a fid to a dentry 29 * @dentry: dentry that the fid is being added to 30 * @pfid: fid to add, NULLed out 31 * 32 */ 33 void v9fs_fid_add(struct dentry *dentry, struct p9_fid **pfid) 34 { 35 struct p9_fid *fid = *pfid; 36 37 spin_lock(&dentry->d_lock); 38 __add_fid(dentry, fid); 39 spin_unlock(&dentry->d_lock); 40 41 *pfid = NULL; 42 } 43 44 static bool v9fs_is_writeable(int mode) 45 { 46 if (mode & (P9_OWRITE|P9_ORDWR)) 47 return true; 48 else 49 return false; 50 } 51 52 /** 53 * v9fs_fid_find_inode - search for an open fid off of the inode list 54 * @inode: return a fid pointing to a specific inode 55 * @want_writeable: only consider fids which are writeable 56 * @uid: return a fid belonging to the specified user 57 * @any: ignore uid as a selection criteria 58 * 59 */ 60 struct p9_fid *v9fs_fid_find_inode(struct inode *inode, bool want_writeable, 61 kuid_t uid, bool any) 62 { 63 struct hlist_head *h; 64 struct p9_fid *fid, *ret = NULL; 65 66 p9_debug(P9_DEBUG_VFS, " inode: %p\n", inode); 67 68 spin_lock(&inode->i_lock); 69 h = (struct hlist_head *)&inode->i_private; 70 hlist_for_each_entry(fid, h, ilist) { 71 if (any || uid_eq(fid->uid, uid)) { 72 if (want_writeable && !v9fs_is_writeable(fid->mode)) { 73 p9_debug(P9_DEBUG_VFS, " mode: %x not writeable?\n", 74 fid->mode); 75 continue; 76 } 77 p9_fid_get(fid); 78 ret = fid; 79 break; 80 } 81 } 82 spin_unlock(&inode->i_lock); 83 return ret; 84 } 85 86 /** 87 * v9fs_open_fid_add - add an open fid to an inode 88 * @inode: inode that the fid is being added to 89 * @pfid: fid to add, NULLed out 90 * 91 */ 92 93 void v9fs_open_fid_add(struct inode *inode, struct p9_fid **pfid) 94 { 95 struct p9_fid *fid = *pfid; 96 97 spin_lock(&inode->i_lock); 98 hlist_add_head(&fid->ilist, (struct hlist_head *)&inode->i_private); 99 spin_unlock(&inode->i_lock); 100 101 *pfid = NULL; 102 } 103 104 105 /** 106 * v9fs_fid_find - retrieve a fid that belongs to the specified uid 107 * @dentry: dentry to look for fid in 108 * @uid: return fid that belongs to the specified user 109 * @any: if non-zero, return any fid associated with the dentry 110 * 111 */ 112 113 static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any) 114 { 115 struct p9_fid *fid, *ret; 116 117 p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p) uid %d any %d\n", 118 dentry, dentry, from_kuid(&init_user_ns, uid), 119 any); 120 ret = NULL; 121 /* we'll recheck under lock if there's anything to look in */ 122 if (dentry->d_fsdata) { 123 struct hlist_head *h = (struct hlist_head *)&dentry->d_fsdata; 124 125 spin_lock(&dentry->d_lock); 126 hlist_for_each_entry(fid, h, dlist) { 127 if (any || uid_eq(fid->uid, uid)) { 128 ret = fid; 129 p9_fid_get(ret); 130 break; 131 } 132 } 133 spin_unlock(&dentry->d_lock); 134 } else { 135 if (dentry->d_inode) 136 ret = v9fs_fid_find_inode(dentry->d_inode, false, uid, any); 137 } 138 139 return ret; 140 } 141 142 /* 143 * We need to hold v9ses->rename_sem as long as we hold references 144 * to returned path array. Array element contain pointers to 145 * dentry names. 146 */ 147 static int build_path_from_dentry(struct v9fs_session_info *v9ses, 148 struct dentry *dentry, const unsigned char ***names) 149 { 150 int n = 0, i; 151 const unsigned char **wnames; 152 struct dentry *ds; 153 154 for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent) 155 n++; 156 157 wnames = kmalloc_array(n, sizeof(char *), GFP_KERNEL); 158 if (!wnames) 159 goto err_out; 160 161 for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent) 162 wnames[i] = ds->d_name.name; 163 164 *names = wnames; 165 return n; 166 err_out: 167 return -ENOMEM; 168 } 169 170 static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry, 171 kuid_t uid, int any) 172 { 173 struct dentry *ds; 174 const unsigned char **wnames, *uname; 175 int i, n, l, access; 176 struct v9fs_session_info *v9ses; 177 struct p9_fid *fid, *root_fid, *old_fid; 178 179 v9ses = v9fs_dentry2v9ses(dentry); 180 access = v9ses->flags & V9FS_ACCESS_MASK; 181 fid = v9fs_fid_find(dentry, uid, any); 182 if (fid) 183 return fid; 184 /* 185 * we don't have a matching fid. To do a TWALK we need 186 * parent fid. We need to prevent rename when we want to 187 * look at the parent. 188 */ 189 down_read(&v9ses->rename_sem); 190 ds = dentry->d_parent; 191 fid = v9fs_fid_find(ds, uid, any); 192 if (fid) { 193 /* Found the parent fid do a lookup with that */ 194 old_fid = fid; 195 196 fid = p9_client_walk(old_fid, 1, &dentry->d_name.name, 1); 197 p9_fid_put(old_fid); 198 goto fid_out; 199 } 200 up_read(&v9ses->rename_sem); 201 202 /* start from the root and try to do a lookup */ 203 root_fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any); 204 if (!root_fid) { 205 /* the user is not attached to the fs yet */ 206 if (access == V9FS_ACCESS_SINGLE) 207 return ERR_PTR(-EPERM); 208 209 if (v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) 210 uname = NULL; 211 else 212 uname = v9ses->uname; 213 214 fid = p9_client_attach(v9ses->clnt, NULL, uname, uid, 215 v9ses->aname); 216 if (IS_ERR(fid)) 217 return fid; 218 219 root_fid = p9_fid_get(fid); 220 v9fs_fid_add(dentry->d_sb->s_root, &fid); 221 } 222 /* If we are root ourself just return that */ 223 if (dentry->d_sb->s_root == dentry) 224 return root_fid; 225 226 /* 227 * Do a multipath walk with attached root. 228 * When walking parent we need to make sure we 229 * don't have a parallel rename happening 230 */ 231 down_read(&v9ses->rename_sem); 232 n = build_path_from_dentry(v9ses, dentry, &wnames); 233 if (n < 0) { 234 fid = ERR_PTR(n); 235 goto err_out; 236 } 237 fid = root_fid; 238 old_fid = root_fid; 239 i = 0; 240 while (i < n) { 241 l = min(n - i, P9_MAXWELEM); 242 /* 243 * We need to hold rename lock when doing a multipath 244 * walk to ensure none of the path components change 245 */ 246 fid = p9_client_walk(old_fid, l, &wnames[i], 247 old_fid == root_fid /* clone */); 248 /* non-cloning walk will return the same fid */ 249 if (fid != old_fid) { 250 p9_fid_put(old_fid); 251 old_fid = fid; 252 } 253 if (IS_ERR(fid)) { 254 kfree(wnames); 255 goto err_out; 256 } 257 i += l; 258 } 259 kfree(wnames); 260 fid_out: 261 if (!IS_ERR(fid)) { 262 spin_lock(&dentry->d_lock); 263 if (d_unhashed(dentry)) { 264 spin_unlock(&dentry->d_lock); 265 p9_fid_put(fid); 266 fid = ERR_PTR(-ENOENT); 267 } else { 268 __add_fid(dentry, fid); 269 p9_fid_get(fid); 270 spin_unlock(&dentry->d_lock); 271 } 272 } 273 err_out: 274 up_read(&v9ses->rename_sem); 275 return fid; 276 } 277 278 /** 279 * v9fs_fid_lookup - lookup for a fid, try to walk if not found 280 * @dentry: dentry to look for fid in 281 * 282 * Look for a fid in the specified dentry for the current user. 283 * If no fid is found, try to create one walking from a fid from the parent 284 * dentry (if it has one), or the root dentry. If the user haven't accessed 285 * the fs yet, attach now and walk from the root. 286 */ 287 288 struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) 289 { 290 kuid_t uid; 291 int any, access; 292 struct v9fs_session_info *v9ses; 293 294 v9ses = v9fs_dentry2v9ses(dentry); 295 access = v9ses->flags & V9FS_ACCESS_MASK; 296 switch (access) { 297 case V9FS_ACCESS_SINGLE: 298 case V9FS_ACCESS_USER: 299 case V9FS_ACCESS_CLIENT: 300 uid = current_fsuid(); 301 any = 0; 302 break; 303 304 case V9FS_ACCESS_ANY: 305 uid = v9ses->uid; 306 any = 1; 307 break; 308 309 default: 310 uid = INVALID_UID; 311 any = 0; 312 break; 313 } 314 return v9fs_fid_lookup_with_uid(dentry, uid, any); 315 } 316 317