1 /*
2  * Copyright (c) 2006-2025 RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  */
9 
10 #include <dfs_seq_file.h>
11 #include <dfs_dentry.h>
12 
13 #define DBG_TAG "DFS.seq"
14 #define DBG_LVL DBG_WARNING
15 #include <rtdbg.h>
16 
17 #ifndef PAGE_SIZE
18 #define PAGE_SIZE 4096
19 #endif
20 
21 /**
22  * @brief Handle buffer overflow condition in sequence file
23  *
24  * @param[in,out] seq Pointer to sequence file structure
25  *
26  * @details Sets the count to size to indicate buffer is full
27  */
dfs_seq_overflow(struct dfs_seq_file * seq)28 static void dfs_seq_overflow(struct dfs_seq_file *seq)
29 {
30     seq->count = seq->size;
31 }
32 
33 /**
34  * @brief Allocate memory for sequence file operations
35  *
36  * @param[in] size Size of memory to allocate in bytes
37  *
38  * @return void* Pointer to allocated memory, or NULL if allocation fails
39  */
dfs_seq_alloc(unsigned long size)40 static void *dfs_seq_alloc(unsigned long size)
41 {
42     return rt_calloc(1, size);
43 }
44 
45 /**
46  * @brief Initialize and open a sequence file
47  *
48  * @param[in] file Pointer to the file structure to be initialized
49  * @param[in] ops Pointer to sequence operations structure containing callback functions
50  *
51  * @return int 0 on success, negative error code on failure:
52  *         -EINVAL if ops is NULL
53  *         -ENOMEM if memory allocation fails
54  */
dfs_seq_open(struct dfs_file * file,const struct dfs_seq_ops * ops)55 int dfs_seq_open(struct dfs_file *file, const struct dfs_seq_ops *ops)
56 {
57     struct dfs_seq_file *seq;
58 
59     if (!ops)
60     {
61         LOG_E("dfs_seq_open: ops = null, pathname: %s\n", file->dentry->pathname);
62         return -EINVAL;
63     }
64 
65     if (file->data)
66     {
67         LOG_W("dfs_seq_open: file->data != null\n");
68     }
69 
70     seq = rt_calloc(1, sizeof(struct dfs_seq_file));
71     if (!seq)
72         return -ENOMEM;
73 
74     file->data = seq;
75 
76     rt_mutex_init(&seq->lock, "dfs_seq", RT_IPC_FLAG_PRIO);
77 
78     seq->ops = ops;
79     seq->file = file;
80 
81     return 0;
82 }
83 
84 /**
85  * @brief Traverse sequence file data with specified offset
86  *
87  * This function traverses the sequence file data starting from the specified offset.
88  * It handles buffer overflow conditions by dynamically resizing the buffer when needed.
89  *
90  * @param[in,out] seq Pointer to sequence file structure
91  * @param[in] offset Position to start traversing from
92  *
93  * @return int 0 on success, negative error code on failure:
94  *         -ENOMEM if memory allocation fails
95  *         -EAGAIN if buffer needs to be resized
96  *
97  * @note Data output loop: start() -> show() -> next() -> show() -> ... -> next() -> stop()
98  */
dfs_seq_traverse(struct dfs_seq_file * seq,off_t offset)99 static int dfs_seq_traverse(struct dfs_seq_file *seq, off_t offset)
100 {
101     off_t pos = 0;
102     int error = 0;
103     void *p;
104 
105     seq->index = 0;
106     seq->count = seq->from = 0;
107     if (!offset)
108         return 0;
109 
110     if (!seq->buf)
111     {
112         seq->buf = dfs_seq_alloc(seq->size = PAGE_SIZE);
113         if (!seq->buf)
114             return -ENOMEM;
115     }
116     p = seq->ops->start(seq, &seq->index);
117     while (p)
118     {
119         error = seq->ops->show(seq, p);
120         if (error < 0)
121             break;
122         if (error)
123         {
124             error = 0;
125             seq->count = 0;
126         }
127         if (dfs_seq_is_full(seq))
128             goto Eoverflow;
129 
130         p = seq->ops->next(seq, p, &seq->index);
131         if (pos + seq->count > offset)
132         {
133             seq->from = offset - pos;
134             seq->count -= seq->from;
135             break;
136         }
137         pos += seq->count;
138         seq->count = 0;
139         if (pos == offset)
140             break;
141     }
142     seq->ops->stop(seq, p);
143     return error;
144 
145 Eoverflow:
146     seq->ops->stop(seq, p);
147     rt_free(seq->buf);
148     seq->count = 0;
149     seq->buf = dfs_seq_alloc(seq->size <<= 1);
150     return !seq->buf ? -ENOMEM : -EAGAIN;
151 }
152 
153 /**
154  * @brief Read data from sequence file
155  *
156  * @param[in] file Pointer to the file structure
157  * @param[out] buf Buffer to store the read data
158  * @param[in] size Size of the buffer in bytes
159  * @param[in,out] pos Current file position (updated after read)
160  *
161  * @return ssize_t Number of bytes read on success, negative error code on failure:
162  *         -EFAULT if buffer error occurs
163  *         -ENOMEM if memory allocation fails
164  *         0 if size is 0
165  *
166  * @details This function implements the core sequence file reading logic with following steps:
167  *          1. Reset iterator if reading from start
168  *          2. Synchronize position if needed
169  *          3. Allocate buffer if not exists
170  *          4. Copy remaining data from previous read
171  *          5. Start iteration and fill buffer with new data
172  *          6. Handle buffer overflow by doubling size
173  *          7. Copy data to user buffer and update positions
174  */
dfs_seq_read(struct dfs_file * file,void * buf,size_t size,off_t * pos)175 ssize_t dfs_seq_read(struct dfs_file *file, void *buf, size_t size, off_t *pos)
176 {
177     struct dfs_seq_file *seq = file->data;
178     size_t copied = 0;
179     size_t n;
180     void *p;
181     int err = 0;
182 
183     if (!size)
184         return 0;
185 
186     rt_mutex_take(&seq->lock, RT_WAITING_FOREVER);
187 
188     /*
189      * if request is to read from zero offset, reset iterator to first
190      * record as it might have been already advanced by previous requests
191      */
192     if (*pos == 0)
193     {
194         seq->index = 0;
195         seq->count = 0;
196     }
197 
198     /* Don't assume ki_pos is where we left it */
199     if (*pos != seq->read_pos)
200     {
201         while ((err = dfs_seq_traverse(seq, *pos)) == -EAGAIN)
202             ;
203         if (err)
204         {
205             /* With prejudice... */
206             seq->read_pos = 0;
207             seq->index = 0;
208             seq->count = 0;
209             goto Done;
210         }
211         else
212         {
213             seq->read_pos = *pos;
214         }
215     }
216 
217     /* grab buffer if we didn't have one */
218     if (!seq->buf)
219     {
220         seq->buf = dfs_seq_alloc(seq->size = PAGE_SIZE);
221         if (!seq->buf)
222             goto Enomem;
223     }
224     /* something left in the buffer - copy it out first */
225     if (seq->count)
226     {
227         n = seq->count > size ? size : seq->count;
228         rt_memcpy((char *)buf + copied, seq->buf + seq->from, n);
229         size -= n;
230         seq->count -= n;
231         seq->from += n;
232         copied += n;
233         if (seq->count) /* hadn't managed to copy everything */
234             goto Done;
235     }
236     /* get a non-empty record in the buffer */
237     seq->from = 0;
238     p = seq->ops->start(seq, &seq->index);
239     while (p)
240     {
241         err = seq->ops->show(seq, p);
242         if (err < 0) /* hard error */
243             break;
244         if (err) /* ->show() says "skip it" */
245             seq->count = 0;
246         if (!seq->count)
247         { /* empty record */
248             p = seq->ops->next(seq, p, &seq->index);
249             continue;
250         }
251         if (!dfs_seq_is_full(seq)) /* got it */
252             goto Fill;
253         /* need a bigger buffer */
254         seq->ops->stop(seq, p);
255         rt_free(seq->buf);
256         seq->count = 0;
257         seq->buf = dfs_seq_alloc(seq->size <<= 1);
258         if (!seq->buf)
259             goto Enomem;
260         p = seq->ops->start(seq, &seq->index);
261     }
262     /* EOF or an error */
263     seq->ops->stop(seq, p);
264     seq->count = 0;
265     goto Done;
266 Fill:
267     /* one non-empty record is in the buffer; if they want more, */
268     /* try to fit more in, but in any case we need to advance */
269     /* the iterator once for every record shown. */
270     while (1)
271     {
272         size_t offs = seq->count;
273         off_t pos = seq->index;
274 
275         p = seq->ops->next(seq, p, &seq->index);
276         if (pos == seq->index)
277         {
278             LOG_W(".next function %p did not update position index\n", seq->ops->next);
279             seq->index++;
280         }
281         if (!p) /* no next record for us */
282             break;
283         if (seq->count >= size)
284             break;
285         err = seq->ops->show(seq, p);
286         if (err > 0)
287         { /* ->show() says "skip it" */
288             seq->count = offs;
289         }
290         else if (err || dfs_seq_is_full(seq))
291         {
292             seq->count = offs;
293             break;
294         }
295     }
296     seq->ops->stop(seq, p);
297     n = seq->count > size ? size : seq->count;
298     rt_memcpy((char *)buf + copied, seq->buf, n);
299     size -= n;
300     copied += n;
301     seq->count -= n;
302     seq->from = n;
303 Done:
304     if (!copied)
305     {
306         copied = seq->count ? -EFAULT : err;
307     }
308     else
309     {
310         *pos += copied;
311         seq->read_pos += copied;
312     }
313     rt_mutex_release(&seq->lock);
314     return copied;
315 Enomem:
316     err = -ENOMEM;
317     goto Done;
318 }
319 
320 /**
321  * @brief Reposition the file offset for sequence file
322  *
323  * @param[in] file Pointer to the file structure
324  * @param[in] offset Offset value according to whence
325  * @param[in] whence Reference position for offset:
326  *                  - SEEK_SET: from file beginning
327  *                  - SEEK_CUR: from current position
328  * @return off_t New file offset on success, negative error code on failure:
329  *         -EINVAL for invalid parameters
330  */
dfs_seq_lseek(struct dfs_file * file,off_t offset,int whence)331 off_t dfs_seq_lseek(struct dfs_file *file, off_t offset, int whence)
332 {
333     struct dfs_seq_file *seq = file->data;
334     off_t retval = -EINVAL;
335 
336     rt_mutex_take(&seq->lock, RT_WAITING_FOREVER);
337 
338     switch (whence)
339     {
340     case SEEK_CUR:
341         offset += file->fpos;
342     case SEEK_SET:
343         if (offset < 0)
344             break;
345         retval = offset;
346         if (offset != seq->read_pos)
347         {
348             while ((retval = dfs_seq_traverse(seq, offset)) == -EAGAIN);
349             if (retval)
350             {
351                 /* with extreme prejudice... */
352                 retval = 0;
353                 seq->read_pos = 0;
354                 seq->index = 0;
355                 seq->count = 0;
356             }
357             else
358             {
359                 seq->read_pos = offset;
360                 retval = offset;
361             }
362         }
363     }
364 
365     rt_mutex_release(&seq->lock);
366 
367     return retval;
368 }
369 
370 /**
371  * @brief Release resources associated with a sequence file
372  *
373  * @param[in] file Pointer to the file structure to be released
374  *
375  * @return int Always returns 0 indicating success
376  */
dfs_seq_release(struct dfs_file * file)377 int dfs_seq_release(struct dfs_file *file)
378 {
379     struct dfs_seq_file *seq = file->data;
380 
381     if (seq)
382     {
383         rt_mutex_detach(&seq->lock);
384         if (seq->buf)
385         {
386             rt_free(seq->buf);
387         }
388         rt_free(seq);
389     }
390 
391     return 0;
392 }
393 
394 /**
395  * @brief Format and write data to sequence file buffer using variable arguments
396  *
397  * @param[in,out] seq Pointer to sequence file structure
398  * @param[in] f Format string (printf-style)
399  * @param[in] args Variable arguments list
400  *
401  * @details This function:
402  *          - Formats data using vsnprintf
403  *          - Triggers overflow if buffer is full
404  */
dfs_seq_vprintf(struct dfs_seq_file * seq,const char * f,va_list args)405 void dfs_seq_vprintf(struct dfs_seq_file *seq, const char *f, va_list args)
406 {
407     int len;
408 
409     if (seq->count < seq->size)
410     {
411         len = vsnprintf(seq->buf + seq->count, seq->size - seq->count, f, args);
412         if (seq->count + len < seq->size)
413         {
414             seq->count += len;
415             return;
416         }
417     }
418     dfs_seq_overflow(seq);
419 }
420 
421 /**
422  * @brief Format and print data to sequence file buffer (printf-style)
423  *
424  * @param[in,out] seq Pointer to sequence file structure
425  * @param[in] f Format string (printf-style)
426  * @param[in] ... Variable arguments matching format string
427  */
dfs_seq_printf(struct dfs_seq_file * seq,const char * f,...)428 void dfs_seq_printf(struct dfs_seq_file *seq, const char *f, ...)
429 {
430     va_list args;
431 
432     va_start(args, f);
433     dfs_seq_vprintf(seq, f, args);
434     va_end(args);
435 }
436 
437 /**
438  * @brief Write a single character to sequence file buffer
439  *
440  * @param[in,out] seq Pointer to sequence file structure
441  * @param[in] c Character to be written
442  */
dfs_seq_putc(struct dfs_seq_file * seq,char c)443 void dfs_seq_putc(struct dfs_seq_file *seq, char c)
444 {
445     if (seq->count < seq->size)
446     {
447         seq->buf[seq->count++] = c;
448     }
449 }
450 
451 /**
452  * @brief Write a string to sequence file buffer
453  *
454  * @param[in,out] seq Pointer to sequence file structure
455  * @param[in] s Null-terminated string to be written
456  */
dfs_seq_puts(struct dfs_seq_file * seq,const char * s)457 void dfs_seq_puts(struct dfs_seq_file *seq, const char *s)
458 {
459     int len = strlen(s);
460 
461     if (seq->count + len >= seq->size)
462     {
463         dfs_seq_overflow(seq);
464         return;
465     }
466     rt_memcpy(seq->buf + seq->count, s, len);
467     seq->count += len;
468 }
469 
470 /**
471  * @brief Write arbitrary binary data to sequence file buffer
472  *
473  * @param[in,out] seq Pointer to sequence file structure
474  * @param[in] data Pointer to data to be written
475  * @param[in] len Length of data in bytes
476  *
477  * @return int 0 on success, -1 if buffer overflow occurs
478  */
dfs_seq_write(struct dfs_seq_file * seq,const void * data,size_t len)479 int dfs_seq_write(struct dfs_seq_file *seq, const void *data, size_t len)
480 {
481     if (seq->count + len < seq->size)
482     {
483         rt_memcpy(seq->buf + seq->count, data, len);
484         seq->count += len;
485         return 0;
486     }
487     dfs_seq_overflow(seq);
488     return -1;
489 }
490 
491 /**
492  * @brief Pad the sequence file buffer with spaces and optionally append a character
493  *
494  * @param[in,out] seq Pointer to sequence file structure
495  * @param[in] c Optional character to append after padding (if not '\0')
496  */
dfs_seq_pad(struct dfs_seq_file * seq,char c)497 void dfs_seq_pad(struct dfs_seq_file *seq, char c)
498 {
499     int size = seq->pad_until - seq->count;
500 
501     if (size > 0)
502     {
503         if (size + seq->count > seq->size)
504         {
505             dfs_seq_overflow(seq);
506             return;
507         }
508         rt_memset(seq->buf + seq->count, ' ', size);
509         seq->count += size;
510     }
511 
512     if (c)
513     {
514         dfs_seq_putc(seq, c);
515     }
516 }