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 } 135 if (!ret && dentry->d_inode) 136 ret = v9fs_fid_find_inode(dentry->d_inode, false, uid, any); 137 138 return ret; 139 } 140 141 /* 142 * We need to hold v9ses->rename_sem as long as we hold references 143 * to returned path array. Array element contain pointers to 144 * dentry names. 145 */ 146 static int build_path_from_dentry(struct v9fs_session_info *v9ses, 147 struct dentry *dentry, const unsigned char ***names) 148 { 149 int n = 0, i; 150 const unsigned char **wnames; 151 struct dentry *ds; 152 153 for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent) 154 n++; 155 156 wnames = kmalloc_array(n, sizeof(char *), GFP_KERNEL); 157 if (!wnames) 158 goto err_out; 159 160 for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent) 161 wnames[i] = ds->d_name.name; 162 163 *names = wnames; 164 return n; 165 err_out: 166 return -ENOMEM; 167 } 168 169 static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry, 170 kuid_t uid, int any) 171 { 172 struct dentry *ds; 173 const unsigned char **wnames, *uname; 174 int i, n, l, access; 175 struct v9fs_session_info *v9ses; 176 struct p9_fid *fid, *root_fid, *old_fid; 177 178 v9ses = v9fs_dentry2v9ses(dentry); 179 access = v9ses->flags & V9FS_ACCESS_MASK; 180 fid = v9fs_fid_find(dentry, uid, any); 181 if (fid) 182 return fid; 183 /* 184 * we don't have a matching fid. To do a TWALK we need 185 * parent fid. We need to prevent rename when we want to 186 * look at the parent. 187 */ 188 down_read(&v9ses->rename_sem); 189 ds = dentry->d_parent; 190 fid = v9fs_fid_find(ds, uid, any); 191 if (fid) { 192 /* Found the parent fid do a lookup with that */ 193 old_fid = fid; 194 195 fid = p9_client_walk(old_fid, 1, &dentry->d_name.name, 1); 196 p9_fid_put(old_fid); 197 goto fid_out; 198 } 199 up_read(&v9ses->rename_sem); 200 201 /* start from the root and try to do a lookup */ 202 root_fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any); 203 if (!root_fid) { 204 /* the user is not attached to the fs yet */ 205 if (access == V9FS_ACCESS_SINGLE) 206 return ERR_PTR(-EPERM); 207 208 if (v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) 209 uname = NULL; 210 else 211 uname = v9ses->uname; 212 213 fid = p9_client_attach(v9ses->clnt, NULL, uname, uid, 214 v9ses->aname); 215 if (IS_ERR(fid)) 216 return fid; 217 218 root_fid = p9_fid_get(fid); 219 v9fs_fid_add(dentry->d_sb->s_root, &fid); 220 } 221 /* If we are root ourself just return that */ 222 if (dentry->d_sb->s_root == dentry) 223 return root_fid; 224 225 /* 226 * Do a multipath walk with attached root. 227 * When walking parent we need to make sure we 228 * don't have a parallel rename happening 229 */ 230 down_read(&v9ses->rename_sem); 231 n = build_path_from_dentry(v9ses, dentry, &wnames); 232 if (n < 0) { 233 fid = ERR_PTR(n); 234 goto err_out; 235 } 236 fid = root_fid; 237 old_fid = root_fid; 238 i = 0; 239 while (i < n) { 240 l = min(n - i, P9_MAXWELEM); 241 /* 242 * We need to hold rename lock when doing a multipath 243 * walk to ensure none of the path components change 244 */ 245 fid = p9_client_walk(old_fid, l, &wnames[i], 246 old_fid == root_fid /* clone */); 247 /* non-cloning walk will return the same fid */ 248 if (fid != old_fid) { 249 p9_fid_put(old_fid); 250 old_fid = fid; 251 } 252 if (IS_ERR(fid)) { 253 kfree(wnames); 254 goto err_out; 255 } 256 i += l; 257 } 258 kfree(wnames); 259 fid_out: 260 if (!IS_ERR(fid)) { 261 spin_lock(&dentry->d_lock); 262 if (d_unhashed(dentry)) { 263 spin_unlock(&dentry->d_lock); 264 p9_fid_put(fid); 265 fid = ERR_PTR(-ENOENT); 266 } else { 267 __add_fid(dentry, fid); 268 p9_fid_get(fid); 269 spin_unlock(&dentry->d_lock); 270 } 271 } 272 err_out: 273 up_read(&v9ses->rename_sem); 274 return fid; 275 } 276 277 /** 278 * v9fs_fid_lookup - lookup for a fid, try to walk if not found 279 * @dentry: dentry to look for fid in 280 * 281 * Look for a fid in the specified dentry for the current user. 282 * If no fid is found, try to create one walking from a fid from the parent 283 * dentry (if it has one), or the root dentry. If the user haven't accessed 284 * the fs yet, attach now and walk from the root. 285 */ 286 287 struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) 288 { 289 kuid_t uid; 290 int any, access; 291 struct v9fs_session_info *v9ses; 292 293 v9ses = v9fs_dentry2v9ses(dentry); 294 access = v9ses->flags & V9FS_ACCESS_MASK; 295 switch (access) { 296 case V9FS_ACCESS_SINGLE: 297 case V9FS_ACCESS_USER: 298 case V9FS_ACCESS_CLIENT: 299 uid = current_fsuid(); 300 any = 0; 301 break; 302 303 case V9FS_ACCESS_ANY: 304 uid = v9ses->uid; 305 any = 1; 306 break; 307 308 default: 309 uid = INVALID_UID; 310 any = 0; 311 break; 312 } 313 return v9fs_fid_lookup_with_uid(dentry, uid, any); 314 } 315 316