source: mainline/uspace/lib/fmgt/src/fmgt.c@ bb4d0b5

Last change on this file since bb4d0b5 was bb4d0b5, checked in by Jiri Svoboda <jiri@…>, 3 weeks ago

Allow user to decide whether to retry or abort when I/O error occurs.

  • Property mode set to 100644
File size: 8.1 KB
Line 
1/*
2 * Copyright (c) 2025 Jiri Svoboda
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 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup fmgt
30 * @{
31 */
32/** @file File management library.
33 */
34
35#include <dirent.h>
36#include <errno.h>
37#include <stdbool.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <stddef.h>
41#include <str.h>
42#include <vfs/vfs.h>
43#include <dirent.h>
44
45#include "fmgt.h"
46
47#define NEWNAME_LEN 64
48#define BUFFER_SIZE 16384
49
50/** Create file management library instance.
51 *
52 * @param rfmgt Place to store pointer to new file management library instance
53 * @return EOK on succcess, ENOMEM if out of memory.
54 */
55errno_t fmgt_create(fmgt_t **rfmgt)
56{
57 fmgt_t *fmgt;
58
59 fmgt = calloc(1, sizeof(fmgt_t));
60 if (fmgt == NULL)
61 return ENOMEM;
62
63 fibril_mutex_initialize(&fmgt->lock);
64 fmgt->timer = fibril_timer_create(&fmgt->lock);
65 if (fmgt->timer == NULL) {
66 free(fmgt);
67 return ENOMEM;
68 }
69
70 *rfmgt = fmgt;
71 return EOK;
72}
73
74/** Create file management library instance.
75 *
76 * @param fmgt File management object
77 * @param cb Callback functions
78 * @param arg Argument to callback functions
79 * @return EOK on succcess, ENOMEM if out of memory.
80 */
81void fmgt_set_cb(fmgt_t *fmgt, fmgt_cb_t *cb, void *arg)
82{
83 fmgt->cb = cb;
84 fmgt->cb_arg = arg;
85}
86
87/** Configure whether to give immediate initial progress update.
88 *
89 * @param fmgt File management object
90 * @param enabled @c true to post and immediate initial progress update
91 */
92void fmgt_set_init_update(fmgt_t *fmgt, bool enabled)
93{
94 fmgt->do_init_update = enabled;
95}
96
97/** Destroy file management library instance.
98 *
99 * @param fmgt File management object
100 */
101void fmgt_destroy(fmgt_t *fmgt)
102{
103 (void)fibril_timer_clear(fmgt->timer);
104 fibril_timer_destroy(fmgt->timer);
105 free(fmgt);
106}
107
108/** Suggest file name for new file.
109 *
110 * @param fmgt File management object
111 * @param rstr Place to store pointer to newly allocated string
112 * @return EOK on success or an error code
113 */
114errno_t fmgt_new_file_suggest(char **rstr)
115{
116 errno_t rc;
117 vfs_stat_t stat;
118 unsigned u;
119 char name[NEWNAME_LEN];
120
121 u = 0;
122 while (true) {
123 snprintf(name, sizeof(name), "noname%02u.txt", u);
124 rc = vfs_stat_path(name, &stat);
125 if (rc != EOK)
126 break;
127
128 ++u;
129 }
130
131 *rstr = str_dup(name);
132 if (*rstr == NULL)
133 return ENOMEM;
134
135 return EOK;
136}
137
138/** Get progress update report.
139 *
140 * @param fmgt File management object
141 * @param progress Place to store progress update
142 */
143static void fmgt_get_progress(fmgt_t *fmgt, fmgt_progress_t *progress)
144{
145 unsigned percent;
146
147 if (fmgt->curf_totalb > 0)
148 percent = fmgt->curf_procb * 100 / fmgt->curf_totalb;
149 else
150 percent = 100;
151
152 capa_blocks_format_buf(fmgt->curf_procb, 1, progress->curf_procb,
153 sizeof(progress->curf_procb));
154 capa_blocks_format_buf(fmgt->curf_totalb, 1, progress->curf_totalb,
155 sizeof(progress->curf_totalb));
156 snprintf(progress->curf_percent, sizeof(progress->curf_percent), "%u%%",
157 percent);
158}
159
160/** Give the caller progress update.
161 *
162 * @param fmgt File management object
163 */
164static void fmgt_progress_update(fmgt_t *fmgt)
165{
166 fmgt_progress_t progress;
167
168 if (fmgt->cb != NULL && fmgt->cb->progress != NULL) {
169 fmgt_get_progress(fmgt, &progress);
170 fmgt->curf_progr = true;
171 fmgt->cb->progress(fmgt->cb_arg, &progress);
172 }
173}
174
175/** Provide initial progress update (if required).
176 *
177 * The caller configures the file management object regarding whether
178 * initial updates are required.
179 *
180 * @param fmgt File management object
181 */
182static void fmgt_initial_progress_update(fmgt_t *fmgt)
183{
184 if (fmgt->do_init_update)
185 fmgt_progress_update(fmgt);
186}
187
188/** Provide final progress update (if required).
189 *
190 * Final update is provided only if a previous progress update was given.
191 *
192 * @param fmgt File management object
193 */
194static void fmgt_final_progress_update(fmgt_t *fmgt)
195{
196 if (fmgt->curf_progr)
197 fmgt_progress_update(fmgt);
198}
199
200/** Progress timer function.
201 *
202 * Periodically called to provide progress updates.
203 *
204 * @param arg Argument (fmgt_t *)
205 */
206static void fmgt_timer_fun(void *arg)
207{
208 fmgt_t *fmgt = (fmgt_t *)arg;
209
210 fmgt_progress_update(fmgt);
211 fibril_timer_set(fmgt->timer, 500000, fmgt_timer_fun, (void *)fmgt);
212}
213
214/** Start progress update timer.
215 *
216 * @param fmgt File management object
217 */
218static void fmgt_timer_start(fmgt_t *fmgt)
219{
220 fibril_timer_set(fmgt->timer, 500000, fmgt_timer_fun, (void *)fmgt);
221}
222
223/** Stop progress update timer.
224 *
225 * @param fmgt File management object
226 */
227static void fmgt_timer_stop(fmgt_t *fmgt)
228{
229 (void)fibril_timer_clear(fmgt->timer);
230}
231
232/** Query caller whether operation should be aborted.
233 *
234 * @param fmgt File management object
235 * @return @c true iff operation should be aborted
236 */
237static bool fmgt_abort_query(fmgt_t *fmgt)
238{
239 if (fmgt->cb != NULL && fmgt->cb->abort_query != NULL)
240 return fmgt->cb->abort_query(fmgt->cb_arg);
241 else
242 return false;
243}
244
245/** Query caller how to recover from I/O error.
246 *
247 * @param fmgt File management object
248 * @param err I/O error report
249 * @return What error recovery action should be taken.
250 */
251static fmgt_error_action_t fmgt_io_error_query(fmgt_t *fmgt,
252 fmgt_io_error_t *err)
253{
254 if (fmgt->cb != NULL && fmgt->cb->io_error_query != NULL)
255 return fmgt->cb->io_error_query(fmgt->cb_arg, err);
256 else
257 return fmgt_er_abort;
258}
259
260/** Create new file.
261 *
262 * @param fmgt File management object
263 * @param fname File name
264 * @param fsize Size of new file (number of zero bytes to fill in)
265 * @param flags New file flags
266 * @return EOK on success or an error code
267 */
268errno_t fmgt_new_file(fmgt_t *fmgt, const char *fname, uint64_t fsize,
269 fmgt_nf_flags_t flags)
270{
271 int fd;
272 size_t nw;
273 aoff64_t pos = 0;
274 uint64_t now;
275 char *buffer;
276 fmgt_io_error_t err;
277 fmgt_error_action_t action;
278 errno_t rc;
279
280 buffer = calloc(BUFFER_SIZE, 1);
281 if (buffer == NULL)
282 return ENOMEM;
283
284 rc = vfs_lookup_open(fname, WALK_REGULAR | WALK_MUST_CREATE,
285 MODE_WRITE, &fd);
286 if (rc != EOK) {
287 free(buffer);
288 return rc;
289 }
290
291 fmgt->curf_procb = 0;
292 fmgt->curf_totalb = fsize;
293 fmgt->curf_progr = false;
294 fmgt_timer_start(fmgt);
295
296 fmgt_initial_progress_update(fmgt);
297
298 /* Create sparse file? */
299 if ((flags & nf_sparse) != 0) {
300 fmgt->curf_procb = fsize - 1;
301 pos = fsize - 1;
302 }
303
304 while (fmgt->curf_procb < fsize) {
305 now = fsize - fmgt->curf_procb;
306 if (now > BUFFER_SIZE)
307 now = BUFFER_SIZE;
308
309 do {
310 rc = vfs_write(fd, &pos, buffer, now, &nw);
311 if (rc == EOK)
312 break;
313
314 /* I/O error */
315 err.fname = fname;
316 err.optype = fmgt_io_write;
317 err.rc = rc;
318 fmgt_timer_stop(fmgt);
319 action = fmgt_io_error_query(fmgt, &err);
320 fmgt_timer_start(fmgt);
321 } while (action == fmgt_er_retry);
322
323 /* Not recovered? */
324 if (rc != EOK) {
325 free(buffer);
326 vfs_put(fd);
327 fmgt_final_progress_update(fmgt);
328 return rc;
329 }
330
331 fmgt->curf_procb += nw;
332
333 /* User requested abort? */
334 if (fmgt_abort_query(fmgt)) {
335 free(buffer);
336 vfs_put(fd);
337 fmgt_final_progress_update(fmgt);
338 return EINTR;
339 }
340 }
341
342 free(buffer);
343 vfs_put(fd);
344 fmgt_final_progress_update(fmgt);
345 return EOK;
346}
347
348/** @}
349 */
Note: See TracBrowser for help on using the repository browser.