File: /usr/src/linux/fs/umsdos/ioctl.c

1     /*
2      *  linux/fs/umsdos/ioctl.c
3      *
4      *  Written 1993 by Jacques Gelinas
5      *
6      *  Extended MS-DOS ioctl directory handling functions
7      */
8     
9     #include <asm/uaccess.h>
10     #include <linux/errno.h>
11     #include <linux/mm.h>
12     #include <linux/kernel.h>
13     #include <linux/sched.h>
14     #include <linux/fs.h>
15     #include <linux/msdos_fs.h>
16     #include <linux/umsdos_fs.h>
17     
18     struct UMSDOS_DIR_ONCE {
19     	struct dirent *ent;
20     	int count;
21     };
22     
23     /*
24      * Record a single entry the first call.
25      * Return -EINVAL the next one.
26      */
27     static int umsdos_ioctl_fill (
28     				     void *buf,
29     				     const char *name,
30     				     int name_len,
31     				     loff_t offset,
32     				     ino_t ino,
33     				     unsigned type)
34     {
35     	int ret = -EINVAL;
36     	struct UMSDOS_DIR_ONCE *d = (struct UMSDOS_DIR_ONCE *) buf;
37     
38     	if (d->count == 0) {
39     		copy_to_user (d->ent->d_name, name, name_len);
40     		put_user ('\0', d->ent->d_name + name_len);
41     		put_user (name_len, &d->ent->d_reclen);
42     		put_user (ino, &d->ent->d_ino);
43     		put_user (offset, &d->ent->d_off);
44     		d->count = 1;
45     		ret = 0;
46     	}
47     	return ret;
48     }
49     
50     
51     /*
52      * Perform special function on a directory
53      */
54     /* #Specification: ioctl / prototypes
55      * The official prototype for the umsdos ioctl on directory
56      * is:
57      * 
58      * int ioctl (
59      * int fd,          // File handle of the directory
60      * int cmd, // command
61      * struct umsdos_ioctl *data)
62      * 
63      * The struct and the commands are defined in linux/umsdos_fs.h.
64      * 
65      * umsdos_progs/umsdosio.c provide an interface in C++ to all
66      * these ioctl. umsdos_progs/udosctl is a small utility showing
67      * all this.
68      * 
69      * These ioctl generally allow one to work on the EMD or the
70      * DOS directory independently. These are essential to implement
71      * the synchronise.
72      */
73     int UMSDOS_ioctl_dir(struct inode *dir, struct file *filp, unsigned int cmd,
74     			unsigned long data_ptr)
75     {
76     	struct dentry *dentry = filp->f_dentry;
77     	struct umsdos_ioctl *idata = (struct umsdos_ioctl *) data_ptr;
78     	int ret;
79     	struct umsdos_ioctl data;
80     
81     Printk(("UMSDOS_ioctl_dir: %s/%s, cmd=%d, data=%08lx\n",
82     dentry->d_parent->d_name.name, dentry->d_name.name, cmd, data_ptr));
83     
84     	/* forward non-umsdos ioctls - this hopefully doesn't cause conflicts */
85     	if (cmd != UMSDOS_GETVERSION
86     	    && cmd != UMSDOS_READDIR_DOS
87     	    && cmd != UMSDOS_READDIR_EMD
88     	    && cmd != UMSDOS_INIT_EMD
89     	    && cmd != UMSDOS_CREAT_EMD
90     	    && cmd != UMSDOS_RENAME_DOS
91     	    && cmd != UMSDOS_UNLINK_EMD
92     	    && cmd != UMSDOS_UNLINK_DOS
93     	    && cmd != UMSDOS_RMDIR_DOS
94     	    && cmd != UMSDOS_STAT_DOS
95     	    && cmd != UMSDOS_DOS_SETUP)
96     		return fat_dir_ioctl (dir, filp, cmd, data_ptr);
97     
98     	/* #Specification: ioctl / access
99     	 * Only root (effective id) is allowed to do IOCTL on directory
100     	 * in UMSDOS. EPERM is returned for other user.
101     	 */
102     	/*
103     	 * Well, not all cases require write access, but it simplifies
104     	 * the code, and let's face it, there is only one client (umssync)
105     	 * for all this.
106     	 */
107     	ret = verify_area (VERIFY_WRITE, (void *) data_ptr, 
108     				sizeof (struct umsdos_ioctl));
109     	if (ret < 0)
110     		goto out;
111     
112     	ret = -EPERM;
113     	if (current->euid != 0 && cmd != UMSDOS_GETVERSION)
114     		goto out;
115     
116     	ret = -EINVAL;
117     	if (cmd == UMSDOS_GETVERSION) {
118     		/* #Specification: ioctl / UMSDOS_GETVERSION
119     		 * The field version and release of the structure
120     		 * umsdos_ioctl are filled with the version and release
121     		 * number of the fs code in the kernel. This will allow
122     		 * some form of checking. Users won't be able to run
123     		 * incompatible utility such as the synchroniser (umssync).
124     		 * umsdos_progs/umsdosio.c enforce this checking.
125     		 * 
126     		 * Return always 0.
127     		 */
128     		put_user (UMSDOS_VERSION, &idata->version);
129     		put_user (UMSDOS_RELEASE, &idata->release);
130     		ret = 0;
131     		goto out;
132     	}
133     	if (cmd == UMSDOS_READDIR_DOS) {
134     		/* #Specification: ioctl / UMSDOS_READDIR_DOS
135     		 * One entry is read from the DOS directory at the current
136     		 * file position. The entry is put as is in the dos_dirent
137     		 * field of struct umsdos_ioctl.
138     		 * 
139     		 * Return > 0 if success.
140     		 */
141     		struct UMSDOS_DIR_ONCE bufk;
142     
143     		bufk.count = 0;
144     		bufk.ent = &idata->dos_dirent;
145     
146     		fat_readdir (filp, &bufk, umsdos_ioctl_fill);
147     
148     		ret = bufk.count == 1 ? 1 : 0;
149     		goto out;
150     	}
151     	if (cmd == UMSDOS_READDIR_EMD) {
152     		/* #Specification: ioctl / UMSDOS_READDIR_EMD
153     		 * One entry is read from the EMD at the current
154     		 * file position. The entry is put as is in the umsdos_dirent
155     		 * field of struct umsdos_ioctl. The corresponding mangled
156     		 * DOS entry name is put in the dos_dirent field.
157     		 * 
158     		 * All entries are read including hidden links. Blank
159     		 * entries are skipped.
160     		 * 
161     		 * Return > 0 if success.
162     		 */
163     		struct dentry *demd;
164     		loff_t pos = filp->f_pos;
165     
166     		/* The absence of the EMD is simply seen as an EOF */
167     		demd = umsdos_get_emd_dentry(dentry);
168     		ret = PTR_ERR(demd);
169     		if (IS_ERR(demd))
170     			goto out;
171     		ret = 0;
172     		if (!demd->d_inode)
173     			goto read_dput;
174     
175     		while (pos < demd->d_inode->i_size) {
176     			off_t f_pos = pos;
177     			struct umsdos_dirent entry;
178     			struct umsdos_info info;
179     
180     			ret = umsdos_emd_dir_readentry (demd, &pos, &entry);
181     
182     			if (ret == -ENAMETOOLONG) {
183     				printk (KERN_INFO "Fixing EMD entry with invalid size -- zeroing out\n");
184     				memset (&info, 0, sizeof (info));
185     				info.f_pos = f_pos;
186     				info.recsize = UMSDOS_REC_SIZE;
187     				ret = umsdos_writeentry (dentry, &info, 1);
188     				continue;
189     			}
190     
191     			if (ret)
192     				break;
193     			if (entry.name_len <= 0)
194     				continue;
195     
196     			umsdos_parse (entry.name, entry.name_len, &info);
197     			info.f_pos = f_pos;
198     			umsdos_manglename (&info);
199     			ret = -EFAULT;
200     			if (copy_to_user (&idata->umsdos_dirent, &entry,
201     							sizeof (entry)))
202     				break;
203     			if (copy_to_user (&idata->dos_dirent.d_name,
204     							info.fake.fname,
205     				 			info.fake.len + 1))
206     				break;
207     			ret = entry.name_len;
208     			break;
209     		}
210     		/* update the original f_pos */
211     		filp->f_pos = pos;
212     	read_dput:
213     		d_drop(demd);
214     		dput(demd);
215     		goto out;
216     	}
217     	if (cmd == UMSDOS_INIT_EMD) {
218     		/* #Specification: ioctl / UMSDOS_INIT_EMD
219     		 * The UMSDOS_INIT_EMD command makes sure the EMD
220     		 * exists for a directory. If it does not, it is
221     		 * created. Also, it makes sure the directory function
222     		 * table (struct inode_operations) is set to the UMSDOS
223     		 * semantic. This mean that umssync may be applied to
224     		 * an "opened" msdos directory, and it will change behavior
225     		 * on the fly.
226     		 * 
227     		 * Return 0 if success.
228     		 */
229     
230     		ret = umsdos_make_emd(dentry);
231     Printk(("UMSDOS_ioctl_dir: INIT_EMD %s/%s, ret=%d\n",
232     dentry->d_parent->d_name.name, dentry->d_name.name, ret));
233     		umsdos_setup_dir (dentry);
234     		goto out;
235     	}
236     
237     	ret = -EFAULT;
238     	if (copy_from_user (&data, idata, sizeof (data)))
239     		goto out;
240     
241     	if (cmd == UMSDOS_CREAT_EMD) {
242     		/* #Specification: ioctl / UMSDOS_CREAT_EMD
243     		 * The umsdos_dirent field of the struct umsdos_ioctl is used
244     		 * as is to create a new entry in the EMD of the directory.
245     		 * The DOS directory is not modified.
246     		 * No validation is done (yet).
247     		 * 
248     		 * Return 0 if success.
249     		 */
250     		struct umsdos_info info;
251     
252     		/* This makes sure info.entry and info in general
253     		 * is correctly initialised
254     		 */
255     		memcpy (&info.entry, &data.umsdos_dirent,
256     			sizeof (data.umsdos_dirent));
257     		umsdos_parse (data.umsdos_dirent.name
258     		    ,data.umsdos_dirent.name_len, &info);
259     		ret = umsdos_newentry (dentry, &info);
260     		goto out;
261     	}
262     	else if (cmd == UMSDOS_RENAME_DOS) {
263     		struct dentry *old_dentry, *new_dentry;		/* FIXME */
264     
265     		/* #Specification: ioctl / UMSDOS_RENAME_DOS
266     		 * A file or directory is renamed in a DOS directory
267     		 * (not moved across directory). The source name
268     		 * is in the dos_dirent.name field and the destination
269     		 * is in umsdos_dirent.name field.
270     		 * 
271     		 * This ioctl allows umssync to rename a mangled file
272     		 * name before syncing it back in the EMD.
273     		 */
274     		old_dentry = umsdos_lookup_dentry (dentry, 
275     						data.dos_dirent.d_name,
276     						data.dos_dirent.d_reclen ,1);
277     		ret = PTR_ERR(old_dentry);
278     		if (IS_ERR(old_dentry))
279     			goto out;
280     		new_dentry = umsdos_lookup_dentry (dentry,
281     						data.umsdos_dirent.name,
282     						data.umsdos_dirent.name_len, 1);
283     		ret = PTR_ERR(new_dentry);
284     		if (!IS_ERR(new_dentry)) {
285     printk("umsdos_ioctl: renaming %s/%s to %s/%s\n",
286     old_dentry->d_parent->d_name.name, old_dentry->d_name.name,
287     new_dentry->d_parent->d_name.name, new_dentry->d_name.name);
288     			ret = msdos_rename (dir, old_dentry, dir, new_dentry);
289     			d_drop(new_dentry);
290     			d_drop(old_dentry);
291     			dput(new_dentry);
292     		}
293     		dput(old_dentry);
294     		goto out;
295     	}
296     	else if (cmd == UMSDOS_UNLINK_EMD) {
297     		/* #Specification: ioctl / UMSDOS_UNLINK_EMD
298     		 * The umsdos_dirent field of the struct umsdos_ioctl is used
299     		 * as is to remove an entry from the EMD of the directory.
300     		 * No validation is done (yet). The mode field is used
301     		 * to validate S_ISDIR or S_ISREG.
302     		 * 
303     		 * Return 0 if success.
304     		 */
305     		struct umsdos_info info;
306     
307     		/* This makes sure info.entry and info in general
308     		 * is correctly initialised
309     		 */
310     		memcpy (&info.entry, &data.umsdos_dirent,
311     			sizeof (data.umsdos_dirent));
312     		umsdos_parse (data.umsdos_dirent.name,
313     				data.umsdos_dirent.name_len, &info);
314     		ret = umsdos_delentry (dentry, &info,
315     				S_ISDIR (data.umsdos_dirent.mode));
316     		if (ret) {
317     			printk(KERN_WARNING
318     				"umsdos_ioctl: delentry %s/%s failed, ret=%d\n",
319     				dentry->d_name.name, info.entry.name, ret);
320     		}
321     		goto out;
322     	}
323     	else if (cmd == UMSDOS_UNLINK_DOS) {
324     		struct dentry *temp;
325     
326     		/* #Specification: ioctl / UMSDOS_UNLINK_DOS
327     		 * The dos_dirent field of the struct umsdos_ioctl is used to
328     		 * execute a msdos_unlink operation. The d_name and d_reclen
329     		 * fields are used.
330     		 * 
331     		 * Return 0 if success.
332     		 */
333     		temp = umsdos_lookup_dentry(dentry, data.dos_dirent.d_name,
334     						data.dos_dirent.d_reclen, 1);
335     		ret = PTR_ERR(temp);
336     		if (IS_ERR(temp))
337     			goto out;
338     		ret = -ENOENT;
339     		if (temp->d_inode) {
340     			ret = -EISDIR;
341     			if (!S_ISDIR(temp->d_inode->i_mode))
342     				ret = msdos_unlink (dir, temp);
343     			if (!ret)
344     				d_delete(temp);
345     		}
346     		dput (temp);
347     		goto out;
348     	}
349     	else if (cmd == UMSDOS_RMDIR_DOS) {
350     		struct dentry *temp;
351     
352     		/* #Specification: ioctl / UMSDOS_RMDIR_DOS
353     		 * The dos_dirent field of the struct umsdos_ioctl is used to
354     		 * execute a msdos_rmdir operation. The d_name and d_reclen
355     		 * fields are used.
356     		 * 
357     		 * Return 0 if success.
358     		 */
359     		temp = umsdos_lookup_dentry(dentry, data.dos_dirent.d_name,
360     					    data.dos_dirent.d_reclen, 1);
361     		ret = PTR_ERR(temp);
362     		if (IS_ERR(temp))
363     			goto out;
364     		ret = -ENOENT;
365     		if (temp->d_inode) {
366     			ret = -ENOTDIR;
367     			if (S_ISDIR(temp->d_inode->i_mode))
368     				ret = msdos_rmdir (dir, temp);
369     			if (!ret)
370     				d_delete(temp);
371     		}
372     		dput (temp);
373     		goto out;
374     
375     	} else if (cmd == UMSDOS_STAT_DOS) {
376     		/* #Specification: ioctl / UMSDOS_STAT_DOS
377     		 * The dos_dirent field of the struct umsdos_ioctl is
378     		 * used to execute a stat operation in the DOS directory.
379     		 * The d_name and d_reclen fields are used.
380     		 * 
381     		 * The following field of umsdos_ioctl.stat are filled.
382     		 * 
383     		 * st_ino,st_mode,st_size,st_atime,st_mtime,st_ctime,
384     		 * Return 0 if success.
385     		 */
386     		struct dentry *dret;
387     		struct inode *inode;
388     
389     		dret = umsdos_lookup_dentry(dentry, data.dos_dirent.d_name,
390     					    data.dos_dirent.d_reclen, 1);
391     		ret = PTR_ERR(dret);
392     		if (IS_ERR(dret))
393     			goto out;
394     		ret = -ENOENT;
395     		inode = dret->d_inode;
396     		if (inode) {
397     			data.stat.st_ino = inode->i_ino;
398     			data.stat.st_mode = inode->i_mode;
399     			data.stat.st_size = inode->i_size;
400     			data.stat.st_atime = inode->i_atime;
401     			data.stat.st_ctime = inode->i_ctime;
402     			data.stat.st_mtime = inode->i_mtime;
403     			ret = -EFAULT;
404     			if (!copy_to_user (&idata->stat, &data.stat, 
405     						sizeof (data.stat)))
406     				ret = 0;
407     		}
408     		dput(dret);
409     		goto out;
410     	}
411     	else if (cmd == UMSDOS_DOS_SETUP) {
412     		/* #Specification: ioctl / UMSDOS_DOS_SETUP
413     		 * The UMSDOS_DOS_SETUP ioctl allow changing the
414     		 * default permission of the MS-DOS filesystem driver
415     		 * on the fly.  The MS-DOS driver applies global permissions
416     		 * to every file and directory. Normally these permissions
417     		 * are controlled by a mount option. This is not
418     		 * available for root partition, so a special utility
419     		 * (umssetup) is provided to do this, normally in
420     		 * /etc/rc.local.
421     		 * 
422     		 * Be aware that this applies ONLY to MS-DOS directories
423     		 * (those without EMD --linux-.---). Umsdos directory
424     		 * have independent (standard) permission for each
425     		 * and every file.
426     		 * 
427     		 * The field umsdos_dirent provide the information needed.
428     		 * umsdos_dirent.uid and gid sets the owner and group.
429     		 * umsdos_dirent.mode set the permissions flags.
430     		 */
431     		dir->i_sb->u.msdos_sb.options.fs_uid = data.umsdos_dirent.uid;
432     		dir->i_sb->u.msdos_sb.options.fs_gid = data.umsdos_dirent.gid;
433     		dir->i_sb->u.msdos_sb.options.fs_umask = data.umsdos_dirent.mode;
434     		ret = 0;
435     	}
436     out:
437     	Printk (("ioctl %d, returning %d\n", cmd, ret));
438     	return ret;
439     }
440