xref: /minix3/minix/lib/libpuffs/link.c (revision ba736c796854b82e29da17267614db0a449419db)
1 #include "fs.h"
2 
3 /*===========================================================================*
4  *				fs_trunc				     *
5  *===========================================================================*/
fs_trunc(ino_t ino_nr,off_t start,off_t end)6 int fs_trunc(ino_t ino_nr, off_t start, off_t end)
7 {
8   int r;
9   struct puffs_node *pn;
10   PUFFS_MAKECRED(pcr, &global_kcred);
11 
12   if ((pn = puffs_pn_nodewalk(global_pu, find_inode_cb, &ino_nr)) == NULL)
13           return(EINVAL);
14 
15   if (end == 0) {
16 	struct vattr va;
17 
18 	if (pn->pn_va.va_size == (u_quad_t) start)
19 		return(OK);
20 
21 	if (global_pu->pu_ops.puffs_node_setattr == NULL)
22 		return(EINVAL);
23 
24 	puffs_vattr_null(&va);
25 	va.va_size = start;
26 
27 	r = global_pu->pu_ops.puffs_node_setattr(global_pu, pn, &va, pcr);
28 	if (r) return(EINVAL);
29   } else {
30 	/* XXX zerofill the given region. Can we make a hole? */
31 	off_t bytes_left = end - start;
32 	char* rw_buf;
33 
34 	if (global_pu->pu_ops.puffs_node_write == NULL)
35 		return(EINVAL);
36 
37 	/* XXX split into chunks? */
38 	rw_buf = malloc(bytes_left);
39 	if (!rw_buf)
40 		panic("fs_ftrunc: failed to allocated memory\n");
41 	memset(rw_buf, 0, bytes_left);
42 
43 	r = global_pu->pu_ops.puffs_node_write(global_pu, pn, (uint8_t *)rw_buf,
44 					start, (size_t *) &bytes_left, pcr, 0);
45 	free(rw_buf);
46 	if (r) return(EINVAL);
47   }
48 
49   update_timens(pn, CTIME | MTIME, NULL);
50 
51   return(r);
52 }
53 
54 
55 /*===========================================================================*
56  *                              fs_link                                      *
57  *===========================================================================*/
fs_link(ino_t dir_nr,char * name,ino_t ino_nr)58 int fs_link(ino_t dir_nr, char *name, ino_t ino_nr)
59 {
60 /* Perform the link(name1, name2) system call. */
61 
62   register int r;
63   struct puffs_node *pn, *pn_dir, *new_pn;
64   struct timespec cur_time;
65   struct puffs_kcn pkcnp;
66   PUFFS_MAKECRED(pcr, &global_kcred);
67   struct puffs_cn pcn = {&pkcnp, (struct puffs_cred *) __UNCONST(pcr), {0,0,0}};
68 
69   if (global_pu->pu_ops.puffs_node_link == NULL)
70 	return(OK);
71 
72   if ((pn = puffs_pn_nodewalk(global_pu, find_inode_cb, &ino_nr)) == NULL)
73 	return(EINVAL);
74 
75   /* Check to see if the file has maximum number of links already. */
76   if (pn->pn_va.va_nlink >= LINK_MAX)
77 	return(EMLINK);
78 
79   /* Linking directories is too dangerous to allow. */
80   if (S_ISDIR(pn->pn_va.va_mode))
81 	return(EPERM);
82 
83   if ((pn_dir = puffs_pn_nodewalk(global_pu, find_inode_cb, &dir_nr)) == NULL)
84         return(EINVAL);
85 
86   if (pn_dir->pn_va.va_nlink == NO_LINK) {
87 	/* Dir does not actually exist */
88         return(ENOENT);
89   }
90 
91   /* If 'name2' exists in full (even if no space) set 'r' to error. */
92   if ((new_pn = advance(pn_dir, name)) == NULL) {
93         r = err_code;
94         if (r == ENOENT) r = OK;
95   } else {
96         r = EEXIST;
97   }
98 
99   if (r != OK) return(r);
100 
101   /* Try to link. */
102   pcn.pcn_namelen = strlen(name);
103   assert(pcn.pcn_namelen <= NAME_MAX);
104   strcpy(pcn.pcn_name, name);
105 
106   if (buildpath) {
107 	if (puffs_path_pcnbuild(global_pu, &pcn, pn_dir) != 0)
108 		return(EINVAL);
109   }
110 
111   if (global_pu->pu_ops.puffs_node_link(global_pu, pn_dir, pn, &pcn) != 0)
112 	r = EINVAL;
113 
114   if (buildpath)
115 	global_pu->pu_pathfree(global_pu, &pcn.pcn_po_full);
116 
117   if (r != OK) return(EINVAL);
118 
119   (void)clock_time(&cur_time);
120   update_timens(pn, CTIME, &cur_time);
121   update_timens(pn_dir, MTIME | CTIME, &cur_time);
122 
123   return(OK);
124 }
125 
126 
127 /*===========================================================================*
128  *                             fs_rdlink                                     *
129  *===========================================================================*/
fs_rdlink(ino_t ino_nr,struct fsdriver_data * data,size_t bytes)130 ssize_t fs_rdlink(ino_t ino_nr, struct fsdriver_data *data, size_t bytes)
131 {
132   register int r;              /* return value */
133   struct puffs_node *pn;
134   char path[PATH_MAX];
135   PUFFS_MAKECRED(pcr, &global_kcred);
136 
137   if (bytes > sizeof(path))
138 	bytes = sizeof(path);
139 
140   if ((pn = puffs_pn_nodewalk(global_pu, find_inode_cb, &ino_nr)) == NULL)
141 	return(EINVAL);
142 
143   if (!S_ISLNK(pn->pn_va.va_mode))
144 	return(EACCES);
145 
146   if (global_pu->pu_ops.puffs_node_readlink == NULL)
147 	return(EINVAL);
148 
149   r = global_pu->pu_ops.puffs_node_readlink(global_pu, pn, pcr, path, &bytes);
150   if (r != OK) {
151 	if (r > 0) r = -r;
152 	return(r);
153   }
154 
155   r = fsdriver_copyout(data, 0, path, bytes);
156 
157   return (r == OK) ? (ssize_t)bytes : r;
158 }
159 
160 
161 /*===========================================================================*
162  *                              fs_rename                                    *
163  *===========================================================================*/
fs_rename(ino_t old_dir_nr,char * old_name,ino_t new_dir_nr,char * new_name)164 int fs_rename(ino_t old_dir_nr, char *old_name, ino_t new_dir_nr,
165 	char *new_name)
166 {
167 /* Perform the rename(name1, name2) system call. */
168   struct puffs_node *old_dirp, *old_ip;      /* ptrs to old dir, file pnodes */
169   struct puffs_node *new_dirp, *new_ip;      /* ptrs to new dir, file pnodes */
170   struct puffs_kcn pkcnp_src;
171   PUFFS_MAKECRED(pcr_src, &global_kcred);
172   struct puffs_cn pcn_src = {&pkcnp_src, (struct puffs_cred *) __UNCONST(pcr_src), {0,0,0}};
173   struct puffs_kcn pkcnp_dest;
174   PUFFS_MAKECRED(pcr_dest, &global_kcred);
175   struct puffs_cn pcn_targ = {&pkcnp_dest, (struct puffs_cred *) __UNCONST(pcr_dest), {0,0,0}};
176   int r = OK;                           /* error flag; initially no error */
177   int odir, ndir;                       /* TRUE iff {old|new} file is dir */
178   int same_pdir;                        /* TRUE iff parent dirs are the same */
179   struct timespec cur_time;
180 
181   if (global_pu->pu_ops.puffs_node_rename == NULL)
182 	return(EINVAL);
183 
184   /* Copy the last component of the old name */
185   pcn_src.pcn_namelen = strlen(old_name);
186   assert(pcn_src.pcn_namelen <= NAME_MAX);
187   strcpy(pcn_src.pcn_name, old_name);
188 
189   /* Copy the last component of the new name */
190   pcn_targ.pcn_namelen = strlen(new_name);
191   assert(pcn_targ.pcn_namelen <= NAME_MAX);
192   strcpy(pcn_targ.pcn_name, new_name);
193 
194   /* Get old dir pnode */
195   if ((old_dirp = puffs_pn_nodewalk(global_pu, find_inode_cb,
196     &old_dir_nr)) == NULL)
197         return(ENOENT);
198 
199   old_ip = advance(old_dirp, pcn_src.pcn_name);
200   if (!old_ip)
201 	return(err_code);
202 
203   if (old_ip->pn_mountpoint)
204 	return(EBUSY);
205 
206   /* Get new dir pnode */
207   if ((new_dirp = puffs_pn_nodewalk(global_pu, find_inode_cb,
208     &new_dir_nr)) == NULL) {
209         return(ENOENT);
210   } else {
211         if (new_dirp->pn_va.va_nlink == NO_LINK) {
212 		/* Dir does not actually exist */
213                 return(ENOENT);
214         }
215   }
216 
217   /* not required to exist */
218   new_ip = advance(new_dirp, pcn_targ.pcn_name);
219 
220   /* If the node does exist, make sure it's not a mountpoint. */
221   if (new_ip != NULL && new_ip->pn_mountpoint)
222 	return(EBUSY);
223 
224   if (old_ip != NULL) {
225 	/* TRUE iff dir */
226 	odir = ((old_ip->pn_va.va_mode & I_TYPE) == I_DIRECTORY);
227   } else {
228 	odir = FALSE;
229   }
230 
231   /* Check for a variety of possible errors. */
232   same_pdir = (old_dirp == new_dirp);
233 
234   /* Some tests apply only if the new path exists. */
235   if (new_ip == NULL) {
236 	if (odir && (new_dirp->pn_va.va_nlink >= SHRT_MAX ||
237 		     new_dirp->pn_va.va_nlink >= LINK_MAX) && !same_pdir) {
238 		return(EMLINK);
239 	}
240   } else {
241 	if (old_ip == new_ip) /* old=new */
242 		return(OK); /* do NOT update directory times in this case */
243 
244 	/* dir ? */
245 	ndir = ((new_ip->pn_va.va_mode & I_TYPE) == I_DIRECTORY);
246 	if (odir == TRUE && ndir == FALSE) return(ENOTDIR);
247 	if (odir == FALSE && ndir == TRUE) return(EISDIR);
248   }
249 
250   /* If a process has another root directory than the system root, we might
251    * "accidently" be moving it's working directory to a place where it's
252    * root directory isn't a super directory of it anymore. This can make
253    * the function chroot useless. If chroot will be used often we should
254    * probably check for it here. */
255 
256   /* The rename will probably work. Only two things can go wrong now:
257    * 1. being unable to remove the new file. (when new file already exists)
258    * 2. being unable to make the new directory entry. (new file doesn't exists)
259    *     [directory has to grow by one block and cannot because the disk
260    *      is completely full].
261    * 3. Something (doubtfully) else depending on the FS.
262    */
263 
264   if (buildpath) {
265 	pcn_src.pcn_po_full = old_ip->pn_po;
266 
267 	if (puffs_path_pcnbuild(global_pu, &pcn_targ, new_dirp) != 0)
268 		return(EINVAL);
269   }
270 
271   r = global_pu->pu_ops.puffs_node_rename(global_pu, old_dirp, old_ip, &pcn_src,
272 						new_dirp, new_ip, &pcn_targ);
273   if (r > 0) r = -r;
274 
275   if (buildpath) {
276 	if (r) {
277 		global_pu->pu_pathfree(global_pu, &pcn_targ.pcn_po_full);
278 	} else {
279 		struct puffs_pathinfo pi;
280 		struct puffs_pathobj po_old;
281 
282 		/* handle this node */
283 		po_old = old_ip->pn_po;
284 		old_ip->pn_po = pcn_targ.pcn_po_full;
285 
286 		if (old_ip->pn_va.va_type != VDIR) {
287 			global_pu->pu_pathfree(global_pu, &po_old);
288 			return(OK);
289 		}
290 
291 		/* handle all child nodes for DIRs */
292 		pi.pi_old = &pcn_src.pcn_po_full;
293 		pi.pi_new = &pcn_targ.pcn_po_full;
294 
295 		PU_LOCK();
296 		if (puffs_pn_nodewalk(global_pu, puffs_path_prefixadj, &pi)
297 				!= NULL) {
298 			/* Actually nomem */
299 			return(EINVAL);
300 		}
301 		PU_UNLOCK();
302 		global_pu->pu_pathfree(global_pu, &po_old);
303 	}
304   }
305 
306   (void)clock_time(&cur_time);
307   update_timens(old_dirp, MTIME | CTIME, &cur_time);
308   update_timens(new_dirp, MTIME | CTIME, &cur_time);
309 
310   /* XXX see release_node comment in fs_unlink */
311   if (new_ip && new_ip->pn_count == 0) {
312 	release_node(global_pu, new_ip);
313   }
314 
315   return(r);
316 }
317 
318 static int remove_dir(struct puffs_node *pn_dir, struct puffs_node *pn,
319 	struct puffs_cn *pcn);
320 static int unlink_file(struct puffs_node *dirp, struct puffs_node *pn,
321 	struct puffs_cn *pcn);
322 
323 /*===========================================================================*
324  *				fs_unlink				     *
325  *===========================================================================*/
fs_unlink(ino_t dir_nr,char * name,int call)326 int fs_unlink(ino_t dir_nr, char *name, int call)
327 {
328 /* Perform the unlink(name) or rmdir(name) system call. The code for these two
329  * is almost the same.  They differ only in some condition testing.
330  */
331   int r;
332   struct puffs_node *pn, *pn_dir;
333   struct timespec cur_time;
334   struct puffs_kcn pkcnp;
335   struct puffs_cn pcn = {&pkcnp, 0, {0,0,0}};
336   PUFFS_KCREDTOCRED(pcn.pcn_cred, &global_kcred);
337 
338   /* Copy the last component */
339   pcn.pcn_namelen = strlen(name);
340   assert(pcn.pcn_namelen <= NAME_MAX);
341   strcpy(pcn.pcn_name, name);
342 
343   if ((pn_dir = puffs_pn_nodewalk(global_pu, find_inode_cb, &dir_nr)) == NULL)
344 	return(EINVAL);
345 
346   /* The last directory exists. Does the file also exist? */
347   pn = advance(pn_dir, pcn.pcn_name);
348   r = err_code;
349 
350   /* If error, return pnode. */
351   if (r != OK)
352         return(r);
353   if (pn->pn_mountpoint)
354 	return EBUSY;
355 
356   /* Now test if the call is allowed, separately for unlink() and rmdir(). */
357   if (call == FSC_UNLINK) {
358 	r = unlink_file(pn_dir, pn, &pcn);
359   } else {
360 	r = remove_dir(pn_dir, pn, &pcn); /* call is RMDIR */
361   }
362 
363   if (pn->pn_va.va_nlink != 0) {
364 	(void)clock_time(&cur_time);
365 	update_timens(pn, CTIME, &cur_time);
366 	update_timens(pn_dir, MTIME | CTIME, &cur_time);
367   }
368 
369   /* XXX Ideally, we should check pn->pn_flags & PUFFS_NODE_REMOVED, but
370    * librefuse doesn't set it (neither manually or via puffs_pn_remove() ).
371    * Thus we just check that "pn_count == 0". Otherwise release_node()
372    * will be called in fs_put().
373    */
374   if (pn->pn_count == 0)
375 	release_node(global_pu, pn);
376 
377   return(r);
378 }
379 
380 
381 /*===========================================================================*
382  *				remove_dir				     *
383  *===========================================================================*/
remove_dir(struct puffs_node * pn_dir,struct puffs_node * pn,struct puffs_cn * pcn)384 static int remove_dir(
385 	struct puffs_node *pn_dir,	/* parent directory */
386 	struct puffs_node *pn,		/* directory to be removed */
387 	struct puffs_cn *pcn		/* Name, creads of directory */
388 )
389 {
390   /* A directory file has to be removed. Five conditions have to met:
391    *	- The file must be a directory
392    *	- The directory must be empty (except for . and ..)
393    *	- The final component of the path must not be . or ..
394    *	- The directory must not be the root of a mounted file system (VFS)
395    *	- The directory must not be anybody's root/working directory (VFS)
396    */
397 
398   /* "." and ".." dentries can be stored in 28 bytes */
399   #define EMPTY_DIR_DENTRIES_SIZE	28
400   int r;
401   char remove_dir_buf[EMPTY_DIR_DENTRIES_SIZE];
402   struct dirent *dent = (struct dirent*) remove_dir_buf;
403   int buf_left = EMPTY_DIR_DENTRIES_SIZE;
404   off_t pos = 0;
405   int eofflag = 0;
406 
407   if (global_pu->pu_ops.puffs_node_rmdir == NULL)
408 	return(EINVAL);
409 
410   if (!S_ISDIR(pn->pn_va.va_mode))
411 	return(ENOTDIR);
412 
413   /* Check if directory is empty */
414   r = global_pu->pu_ops.puffs_node_readdir(global_pu, pn, dent, &pos,
415 			(size_t *)&buf_left, pcn->pcn_cred, &eofflag, 0, 0);
416   if (r) return(EINVAL);
417   if (!eofflag) return(ENOTEMPTY);
418 
419   if (pn->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid)
420 	return(EBUSY); /* can't remove 'root' */
421 
422   if (buildpath) {
423 	r = puffs_path_pcnbuild(global_pu, pcn, pn_dir);
424 	if (r) return(EINVAL);
425   }
426 
427   r = global_pu->pu_ops.puffs_node_rmdir(global_pu, pn_dir, pn, pcn);
428 
429   global_pu->pu_pathfree(global_pu, &pcn->pcn_po_full);
430 
431   if (r) return(EINVAL);
432 
433   return(OK);
434 }
435 
436 
437 /*===========================================================================*
438  *				unlink_file				     *
439  *===========================================================================*/
unlink_file(struct puffs_node * dirp,struct puffs_node * pn,struct puffs_cn * pcn)440 static int unlink_file(
441 	struct puffs_node *dirp,	/* parent directory of file */
442 	struct puffs_node *pn,		/* pnode of file, may be NULL too. */
443 	struct puffs_cn *pcn		/* Name, creads of file */
444 )
445 {
446 /* Unlink 'file_name'; pn must be the pnode of 'file_name' */
447   int	r;
448 
449   assert(pn != NULL);
450 
451   if (global_pu->pu_ops.puffs_node_remove == NULL)
452 	return(EINVAL);
453 
454   if (S_ISDIR(pn->pn_va.va_mode))
455 	return(EPERM);
456 
457   if (buildpath) {
458 	r = puffs_path_pcnbuild(global_pu, pcn, dirp);
459 	if (r)
460 		return(EINVAL);
461   }
462 
463   r = global_pu->pu_ops.puffs_node_remove(global_pu, dirp, pn, pcn);
464 
465   global_pu->pu_pathfree(global_pu, &pcn->pcn_po_full);
466 
467   if (r) return(EINVAL);
468 
469   return(OK);
470 }
471