1/*
2 * Copyright (c) 2002 Maxim Sobolev
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include "stand.h"
28
29#define NTRIES		(3)
30#define CONF_BUF	(512)
31#define SEEK_BUF	(512)
32
33struct split_file
34{
35    char  **filesv;	/* Filenames */
36    char  **descsv;	/* Descriptions */
37    int	  filesc;	/* Number of parts */
38    int	  curfile;	/* Current file number */
39    int	  curfd;	/* Current file descriptor */
40    off_t tot_pos;	/* Offset from the beginning of the sequence */
41    off_t file_pos;	/* Offset from the beginning of the slice */
42};
43
44static int	split_openfile(struct split_file *sf);
45static int	splitfs_open(const char *path, struct open_file *f);
46static int	splitfs_close(struct open_file *f);
47static int	splitfs_read(struct open_file *f, void *buf, size_t size, size_t *resid);
48static off_t	splitfs_seek(struct open_file *f, off_t offset, int where);
49static int	splitfs_stat(struct open_file *f, struct stat *sb);
50
51struct fs_ops splitfs_fsops = {
52	.fs_name = "split",
53	.fo_open = splitfs_open,
54	.fo_close = splitfs_close,
55	.fo_read = splitfs_read,
56	.fo_write = null_write,
57	.fo_seek = splitfs_seek,
58	.fo_stat = splitfs_stat,
59	.fo_readdir = null_readdir,
60};
61
62static void
63split_file_destroy(struct split_file *sf)
64{
65    int i;
66
67    if (sf->filesc > 0) {
68	for (i = 0; i < sf->filesc; i++) {
69	    free(sf->filesv[i]);
70	    free(sf->descsv[i]);
71	}
72	free(sf->filesv);
73	free(sf->descsv);
74    }
75    free(sf);
76}
77
78static int
79split_openfile(struct split_file *sf)
80{
81    int i;
82
83    for (i = 0;; i++) {
84	sf->curfd = open(sf->filesv[sf->curfile], O_RDONLY);
85	if (sf->curfd >= 0)
86	    break;
87	if ((sf->curfd == -1) && (errno != ENOENT))
88	    return (errno);
89	if (i == NTRIES)
90	    return (EIO);
91	printf("\nInsert disk labelled %s and press any key...",
92	    sf->descsv[sf->curfile]);
93	getchar();
94	putchar('\n');
95    }
96    sf->file_pos = 0;
97    return (0);
98}
99
100static int
101splitfs_open(const char *fname, struct open_file *f)
102{
103    char *buf, *confname, *cp;
104    int	conffd;
105    struct split_file *sf;
106    struct stat sb;
107
108    /* Have to be in "just read it" mode */
109    if (f->f_flags != F_READ)
110	return(EPERM);
111
112    /* If the name already ends in `.split', ignore it */
113    if ((cp = strrchr(fname, '.')) && (!strcmp(cp, ".split")))
114	return(ENOENT);
115
116    /* Construct new name */
117    confname = malloc(strlen(fname) + 7);
118    sprintf(confname, "%s.split", fname);
119
120    /* Try to open the configuration file */
121    conffd = open(confname, O_RDONLY);
122    free(confname);
123    if (conffd == -1)
124	return(ENOENT);
125
126    if (fstat(conffd, &sb) < 0) {
127	printf("splitfs_open: stat failed\n");
128	close(conffd);
129	return(ENOENT);
130    }
131    if (!S_ISREG(sb.st_mode)) {
132	printf("splitfs_open: not a file\n");
133	close(conffd);
134	return(EISDIR);			/* best guess */
135    }
136
137    /* Allocate a split_file structure, populate it from the config file */
138    sf = malloc(sizeof(struct split_file));
139    bzero(sf, sizeof(struct split_file));
140    buf = malloc(CONF_BUF);
141    while (fgetstr(buf, CONF_BUF, conffd) > 0) {
142	cp = buf;
143	while ((*cp != '\0') && (isspace(*cp) == 0))
144	    cp++;
145	if (*cp != '\0') {
146	    *cp = '\0';
147	    cp++;
148	}
149	while ((*cp != '\0') && (isspace(*cp) != 0))
150	    cp++;
151	if (*cp == '\0')
152	    cp = buf;
153	sf->filesc++;
154	sf->filesv = realloc(sf->filesv, sizeof(*(sf->filesv)) * sf->filesc);
155	sf->descsv = realloc(sf->descsv, sizeof(*(sf->descsv)) * sf->filesc);
156	sf->filesv[sf->filesc - 1] = strdup(buf);
157	sf->descsv[sf->filesc - 1] = strdup(cp);
158    }
159    free(buf);
160    close(conffd);
161
162    if (sf->filesc == 0) {
163	split_file_destroy(sf);
164	return(ENOENT);
165    }
166    errno = split_openfile(sf);
167    if (errno != 0) {
168	split_file_destroy(sf);
169	return(ENOENT);
170    }
171
172    /* Looks OK, we'll take it */
173    f->f_fsdata = sf;
174    return (0);
175}
176
177static int
178splitfs_close(struct open_file *f)
179{
180    int fd;
181    struct split_file *sf;
182
183    sf = (struct split_file *)f->f_fsdata;
184    fd = sf->curfd;
185    split_file_destroy(sf);
186    return(close(fd));
187}
188
189static int
190splitfs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
191{
192    ssize_t nread;
193    size_t totread;
194    struct split_file *sf;
195
196    sf = (struct split_file *)f->f_fsdata;
197    totread = 0;
198    do {
199	nread = read(sf->curfd, buf, size - totread);
200
201	/* Error? */
202	if (nread == -1)
203	    return (errno);
204
205	sf->tot_pos += nread;
206	sf->file_pos += nread;
207	totread += nread;
208	buf = (char *)buf + nread;
209
210	if (totread < size) {				/* EOF */
211	    if (sf->curfile == (sf->filesc - 1))	/* Last slice */
212		break;
213
214	    /* Close previous slice */
215	    if (close(sf->curfd) != 0)
216		return (errno);
217
218	    sf->curfile++;
219	    errno = split_openfile(sf);
220	    if (errno)
221		    return (errno);
222	}
223    } while (totread < size);
224
225    if (resid != NULL)
226	*resid = size - totread;
227
228    return (0);
229}
230
231static off_t
232splitfs_seek(struct open_file *f, off_t offset, int where)
233{
234    int nread;
235    size_t resid;
236    off_t new_pos, seek_by;
237    struct split_file *sf;
238
239    sf = (struct split_file *)f->f_fsdata;
240
241    seek_by = offset;
242    switch (where) {
243    case SEEK_SET:
244	seek_by -= sf->tot_pos;
245	break;
246    case SEEK_CUR:
247	break;
248    case SEEK_END:
249	panic("splitfs_seek: SEEK_END not supported");
250	break;
251    default:
252	errno = EINVAL;
253	return (-1);
254    }
255
256    if (seek_by > 0) {
257	/*
258	 * Seek forward - implemented using splitfs_read(), because otherwise we'll be
259	 * unable to detect that we have crossed slice boundary and hence
260	 * unable to do a long seek crossing that boundary.
261	 */
262	void *tmp;
263
264	tmp = malloc(SEEK_BUF);
265	if (tmp == NULL) {
266	    errno = ENOMEM;
267	    return (-1);
268	}
269
270	nread = 0;
271	for (; seek_by > 0; seek_by -= nread) {
272	    resid = 0;
273	    errno = splitfs_read(f, tmp, min(seek_by, SEEK_BUF), &resid);
274	    nread = min(seek_by, SEEK_BUF) - resid;
275	    if ((errno != 0) || (nread == 0))
276		/* Error or EOF */
277		break;
278	}
279	free(tmp);
280	if (errno != 0)
281	    return (-1);
282    }
283
284    if (seek_by != 0) {
285	/* Seek backward or seek past the boundary of the last slice */
286	if (sf->file_pos + seek_by < 0)
287	    panic("splitfs_seek: can't seek past the beginning of the slice");
288	new_pos = lseek(sf->curfd, seek_by, SEEK_CUR);
289	if (new_pos < 0) {
290	    errno = EINVAL;
291	    return (-1);
292	}
293	sf->tot_pos += new_pos - sf->file_pos;
294	sf->file_pos = new_pos;
295    }
296
297    return (sf->tot_pos);
298}
299
300static int
301splitfs_stat(struct open_file *f, struct stat *sb)
302{
303    int	result;
304    struct split_file *sf = (struct split_file *)f->f_fsdata;
305
306    /* stat as normal, but indicate that size is unknown */
307    if ((result = fstat(sf->curfd, sb)) == 0)
308	sb->st_size = -1;
309    return (result);
310}
311