[PATCH] new helpers for /proc
[opensuse:kernel.git] / fs / seq_file.c
1 /*
2  * linux/fs/seq_file.c
3  *
4  * helper functions for making syntetic files from sequences of records.
5  * initial implementation -- AV, Oct 2001.
6  */
7
8 #include <linux/fs.h>
9 #include <linux/seq_file.h>
10 #include <linux/slab.h>
11
12 #include <asm/uaccess.h>
13
14 /**
15  *      seq_open -      initialize sequential file
16  *      @file: file we initialize
17  *      @op: method table describing the sequence
18  *
19  *      seq_open() sets @file, associating it with a sequence described
20  *      by @op.  @op->start() sets the iterator up and returns the first
21  *      element of sequence. @op->stop() shuts it down.  @op->next()
22  *      returns the next element of sequence.  @op->show() prints element
23  *      into the buffer.  In case of error ->start() and ->next() return
24  *      ERR_PTR(error).  In the end of sequence they return %NULL. ->show()
25  *      returns 0 in case of success and negative number in case of error.
26  */
27 int seq_open(struct file *file, struct seq_operations *op)
28 {
29         struct seq_file *p = kmalloc(sizeof(*p), GFP_KERNEL);
30         if (!p)
31                 return -ENOMEM;
32         memset(p, 0, sizeof(*p));
33         sema_init(&p->sem, 1);
34         p->op = op;
35         file->private_data = p;
36         return 0;
37 }
38
39 /**
40  *      seq_read -      ->read() method for sequential files.
41  *      @file, @buf, @size, @ppos: see file_operations method
42  *
43  *      Ready-made ->f_op->read()
44  */
45 ssize_t seq_read(struct file *file, char *buf, size_t size, loff_t *ppos)
46 {
47         struct seq_file *m = (struct seq_file *)file->private_data;
48         size_t copied = 0;
49         loff_t pos;
50         size_t n;
51         void *p;
52         int err = 0;
53
54         if (ppos != &file->f_pos)
55                 return -EPIPE;
56
57         down(&m->sem);
58         /* grab buffer if we didn't have one */
59         if (!m->buf) {
60                 m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
61                 if (!m->buf)
62                         goto Enomem;
63         }
64         /* if not empty - flush it first */
65         if (m->count) {
66                 n = min(m->count, size);
67                 err = copy_to_user(buf, m->buf + m->from, n);
68                 if (err)
69                         goto Efault;
70                 m->count -= n;
71                 m->from += n;
72                 size -= n;
73                 buf += n;
74                 copied += n;
75                 if (!m->count)
76                         m->index++;
77                 if (!size)
78                         goto Done;
79         }
80         /* we need at least one record in buffer */
81         while (1) {
82                 pos = m->index;
83                 p = m->op->start(m, &pos);
84                 err = PTR_ERR(p);
85                 if (!p || IS_ERR(p))
86                         break;
87                 err = m->op->show(m, p);
88                 if (err)
89                         break;
90                 if (m->count < m->size)
91                         goto Fill;
92                 m->op->stop(m, p);
93                 kfree(m->buf);
94                 m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
95                 if (!m->buf)
96                         goto Enomem;
97         }
98         m->op->stop(m, p);
99         goto Done;
100 Fill:
101         /* they want more? let's try to get some more */
102         while (m->count < size) {
103                 size_t offs = m->count;
104                 loff_t next = pos;
105                 p = m->op->next(m, p, &next);
106                 if (!p || IS_ERR(p)) {
107                         err = PTR_ERR(p);
108                         break;
109                 }
110                 err = m->op->show(m, p);
111                 if (err || m->count == m->size) {
112                         m->count = offs;
113                         break;
114                 }
115                 pos = next;
116         }
117         m->op->stop(m, p);
118         n = min(m->count, size);
119         err = copy_to_user(buf, m->buf, n);
120         if (err)
121                 goto Efault;
122         copied += n;
123         m->count -= n;
124         if (m->count)
125                 m->from = n;
126         else
127                 pos++;
128         m->index = pos;
129 Done:
130         if (!copied)
131                 copied = err;
132         else
133                 *ppos += copied;
134         up(&m->sem);
135         return copied;
136 Enomem:
137         err = -ENOMEM;
138         goto Done;
139 Efault:
140         err = -EFAULT;
141         goto Done;
142 }
143
144 static int traverse(struct seq_file *m, loff_t offset)
145 {
146         loff_t pos = 0;
147         int error = 0;
148         void *p;
149
150         m->index = 0;
151         m->count = m->from = 0;
152         if (!offset)
153                 return 0;
154         if (!m->buf) {
155                 m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
156                 if (!m->buf)
157                         return -ENOMEM;
158         }
159         p = m->op->start(m, &m->index);
160         while (p) {
161                 error = PTR_ERR(p);
162                 if (IS_ERR(p))
163                         break;
164                 error = m->op->show(m, p);
165                 if (error)
166                         break;
167                 if (m->count == m->size)
168                         goto Eoverflow;
169                 if (pos + m->count > offset) {
170                         m->from = offset - pos;
171                         m->count -= m->from;
172                         break;
173                 }
174                 pos += m->count;
175                 m->count = 0;
176                 if (pos == offset) {
177                         m->index++;
178                         break;
179                 }
180                 p = m->op->next(m, p, &m->index);
181         }
182         m->op->stop(m, p);
183         return error;
184
185 Eoverflow:
186         m->op->stop(m, p);
187         kfree(m->buf);
188         m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
189         return !m->buf ? -ENOMEM : -EAGAIN;
190 }
191
192 /**
193  *      seq_lseek -     ->llseek() method for sequential files.
194  *      @file, @offset, @origin: see file_operations method
195  *
196  *      Ready-made ->f_op->llseek()
197  */
198 loff_t seq_lseek(struct file *file, loff_t offset, int origin)
199 {
200         struct seq_file *m = (struct seq_file *)file->private_data;
201         long long retval = -EINVAL;
202
203         down(&m->sem);
204         switch (origin) {
205                 case 1:
206                         offset += file->f_pos;
207                 case 0:
208                         if (offset < 0)
209                                 break;
210                         retval = offset;
211                         if (offset != file->f_pos) {
212                                 while ((retval=traverse(m, offset)) == -EAGAIN)
213                                         ;
214                                 if (retval) {
215                                         /* with extreme perjudice... */
216                                         file->f_pos = 0;
217                                         m->index = 0;
218                                         m->count = 0;
219                                 } else {
220                                         retval = file->f_pos = offset;
221                                 }
222                         }
223         }
224         up(&m->sem);
225         return retval;
226 }
227
228 /**
229  *      seq_release -   free the structures associated with sequential file.
230  *      @file: file in question
231  *      @inode: file->f_dentry->d_inode
232  *
233  *      Frees the structures associated with sequential file; can be used
234  *      as ->f_op->release() if you don't have private data to destroy.
235  */
236 int seq_release(struct inode *inode, struct file *file)
237 {
238         struct seq_file *m = (struct seq_file *)file->private_data;
239         kfree(m->buf);
240         kfree(m);
241         return 0;
242 }
243
244 /**
245  *      seq_escape -    print string into buffer, escaping some characters
246  *      @m:     target buffer
247  *      @s:     string
248  *      @esc:   set of characters that need escaping
249  *
250  *      Puts string into buffer, replacing each occurence of character from
251  *      @esc with usual octal escape.  Returns 0 in case of success, -1 - in
252  *      case of overflow.
253  */
254 int seq_escape(struct seq_file *m, const char *s, const char *esc)
255 {
256         char *end = m->buf + m->size;
257         char *p;
258         char c;
259
260         for (p = m->buf + m->count; (c = *s) != '\0' && p < end; s++) {
261                 if (!strchr(esc, c)) {
262                         *p++ = c;
263                         continue;
264                 }
265                 if (p + 3 < end) {
266                         *p++ = '\\';
267                         *p++ = '0' + ((c & 0300) >> 6);
268                         *p++ = '0' + ((c & 070) >> 3);
269                         *p++ = '0' + (c & 07);
270                         continue;
271                 }
272                 m->count = m->size;
273                 return -1;
274         }
275         m->count = p - m->buf;
276         return 0;
277 }
278
279 int seq_printf(struct seq_file *m, const char *f, ...)
280 {
281         va_list args;
282         int len;
283
284         if (m->count < m->size) {
285                 va_start(args, f);
286                 len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);
287                 va_end(args);
288                 if (m->count + len < m->size) {
289                         m->count += len;
290                         return 0;
291                 }
292         }
293         m->count = m->size;
294         return -1;
295 }
296
297 static void *single_start(struct seq_file *p, loff_t *pos)
298 {
299         return NULL + (*pos == 0);
300 }
301
302 static void *single_next(struct seq_file *p, void *v, loff_t *pos)
303 {
304         ++*pos;
305         return NULL;
306 }
307
308 static void single_stop(struct seq_file *p, void *v)
309 {
310 }
311
312 int single_open(struct file *file, int (*show)(struct seq_file *, void*), void *data)
313 {
314         struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL);
315         int res = -ENOMEM;
316
317         if (op) {
318                 op->start = single_start;
319                 op->next = single_next;
320                 op->stop = single_stop;
321                 op->show = show;
322                 res = seq_open(file, op);
323                 if (!res)
324                         ((struct seq_file *)file->private_data)->private = data;
325                 else
326                         kfree(op);
327         }
328         return res;
329 }
330
331 int single_release(struct inode *inode, struct file *file)
332 {
333         struct seq_operations *op = ((struct seq_file *)file->private_data)->op;
334         int res = seq_release(inode, file);
335         kfree(op);
336         return res;
337 }