source: rtems/cpukit/libfs/src/dosfs/msdos_dir.c @ dfd879fa

5
Last change on this file since dfd879fa was dfd879fa, checked in by Sebastian Huber <sebastian.huber@…>, on 03/16/17 at 10:59:23

dosfs: Fix race condition msdos_dir_read()

Obtain file system instance lock before member access.

Update #2937.

  • Property mode set to 100644
File size: 14.6 KB
Line 
1/**
2 * @file
3 *
4 * @brief MSDOS Directory Handlers Implementation
5 * @ingroup libfs_msdos MSDOS FileSystem
6 */
7
8/*
9 *  Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
10 *  Author: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
11 *
12 *  Modifications to support UTF-8 in the file system are
13 *  Copyright (c) 2013 embedded brains GmbH.
14 *
15 *  The license and distribution terms for this file may be
16 *  found in the file LICENSE in this distribution or at
17 *  http://www.rtems.org/license/LICENSE.
18 */
19#if HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include <ctype.h>
24#include <stdlib.h>
25#include <unistd.h>
26#include <errno.h>
27#include <rtems/libio_.h>
28#include <sys/types.h>
29#include <sys/stat.h>
30
31#include <dirent.h>
32
33#include "fat.h"
34#include "fat_fat_operations.h"
35#include "fat_file.h"
36
37#include "msdos.h"
38
39
40
41/*  msdos_dir_read --
42 *      This routine will read the next directory entry based on the directory
43 *      offset. The offset should be equal to -n- time the size of an
44 *      individual dirent structure. If n is not an integer multiple of the
45 *      sizeof a dirent structure, an integer division will be performed to
46 *      determine directory entry that will be returned in the buffer. Count
47 *      should reflect -m- times the sizeof dirent bytes to be placed in the
48 *      buffer.
49 *      If there are not -m- dirent elements from the current directory
50 *      position to the end of the exisiting file, the remaining entries will
51 *      be placed in the buffer and the returned value will be equal to
52 *      -m actual- times the size of a directory entry.
53 *
54 * PARAMETERS:
55 *     iop    - file control block
56 *     buffer - buffer provided by user
57 *     count  - count of bytes to read
58 *
59 * RETURNS:
60 *     the number of bytes read on success, or -1 if error occured (errno
61 *     set apropriately).
62 */
63ssize_t
64msdos_dir_read(rtems_libio_t *iop, void *buffer, size_t count)
65{
66    int                rc = RC_OK;
67    int                eno = 0;
68    rtems_status_code  sc = RTEMS_SUCCESSFUL;
69    msdos_fs_info_t   *fs_info = iop->pathinfo.mt_entry->fs_info;
70    rtems_dosfs_convert_control *converter = fs_info->converter;
71    const rtems_dosfs_convert_handler *convert_handler = converter->handler;
72    fat_file_fd_t     *fat_fd = iop->pathinfo.node_access;
73    fat_file_fd_t     *tmp_fat_fd = NULL;
74    struct dirent      tmp_dirent;
75    size_t             tmp_lfn_len = 0;
76    uint16_t          *lfn_buf = converter->buffer.data;
77    char              *sfn_buf = converter->buffer.data;
78    const size_t       buf_size = converter->buffer.size;
79    uint32_t           start = 0;
80    ssize_t            ret = 0;
81    ssize_t            cmpltd = 0;
82    uint32_t           j = 0, i = 0;
83    uint32_t           bts2rd = 0;
84    uint32_t           cur_cln = 0;
85    uint32_t           lfn_start = FAT_FILE_SHORT_NAME;
86    uint8_t            lfn_checksum = 0;
87    int                lfn_entries = 0;
88    size_t             string_size = sizeof(tmp_dirent.d_name);
89    bool               is_first_entry;
90
91    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
92                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
93    if (sc != RTEMS_SUCCESSFUL)
94        rtems_set_errno_and_return_minus_one(EIO);
95
96    /*
97     * cast start and count - protect against using sizes that are not exact
98     * multiples of the -dirent- size. These could result in unexpected
99     * results
100     */
101    start = iop->offset / sizeof(struct dirent);
102    count = (count / sizeof(struct dirent)) * sizeof(struct dirent);
103
104    /*
105     * optimization: we know that root directory for FAT12/16 volumes is
106     * sequential set of sectors and any cluster is sequential set of sectors
107     * too, so read such set of sectors is quick operation for low-level IO
108     * layer.
109     */
110    bts2rd = (FAT_FD_OF_ROOT_DIR(fat_fd) &&
111             (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16))) ?
112             fat_fd->fat_file_size                              :
113             fs_info->fat.vol.bpc;
114
115    while (count > 0 && cmpltd >= 0)
116    {
117        /*
118         * fat-file is already opened by open call, so read it
119         * Always read directory fat-file from the beggining because of MSDOS
120         * directories feature :( - we should count elements currently
121         * present in the directory because there may be holes :)
122         */
123        ret = fat_file_read(&fs_info->fat, fat_fd, (j * bts2rd),
124                            bts2rd, fs_info->cl_buf);
125        if (ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
126        {
127            rtems_semaphore_release(fs_info->vol_sema);
128            rtems_set_errno_and_return_minus_one(EIO);
129        }
130
131        for (i = 0; i < ret && cmpltd >= 0; i += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
132        {
133            char* entry = (char*) fs_info->cl_buf + i;
134
135            /*
136             * Is this directory from here on empty ?
137             */
138            if ((*MSDOS_DIR_ENTRY_TYPE(entry)) ==
139                MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY)
140            {
141                rtems_semaphore_release(fs_info->vol_sema);
142                return cmpltd;
143            }
144
145            /* Is the directory entry empty */
146            if ((*MSDOS_DIR_ENTRY_TYPE(entry)) == MSDOS_THIS_DIR_ENTRY_EMPTY)
147                continue;
148
149            /* Is the directory entry empty a volume label */
150            if (((*MSDOS_DIR_ATTR(entry)) & MSDOS_ATTR_VOLUME_ID) &&
151                ((*MSDOS_DIR_ATTR(entry) & MSDOS_ATTR_LFN_MASK) != MSDOS_ATTR_LFN))
152                continue;
153
154            /*
155             * Check the attribute to see if the entry is for a long file
156             * name.
157             */
158            if ((*MSDOS_DIR_ATTR(entry) & MSDOS_ATTR_LFN_MASK) ==
159                MSDOS_ATTR_LFN)
160            {
161                int offset_lfn;
162
163                /*
164                 * Is this is the first entry of a LFN ?
165                 */
166                if (lfn_start == FAT_FILE_SHORT_NAME)
167                {
168                    is_first_entry = true;
169                    /*
170                     * The first entry must have the last long entry flag set.
171                     */
172                    if ((*MSDOS_DIR_ENTRY_TYPE(entry) &
173                         MSDOS_LAST_LONG_ENTRY) == 0)
174                        continue;
175
176                    /*
177                     * Remember the start location of the long file name.
178                     */
179                    lfn_start =
180                      ((j * bts2rd) + i) / MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;
181
182                    /*
183                     * Get the number of entries so we can count down and
184                     * also the checksum of the short entry.
185                     */
186                    lfn_entries = (*MSDOS_DIR_ENTRY_TYPE(entry) &
187                                   MSDOS_LAST_LONG_ENTRY_MASK);
188                    tmp_lfn_len = 0;
189                    lfn_checksum = *MSDOS_DIR_LFN_CHECKSUM(entry);
190                    memset (tmp_dirent.d_name, 0, sizeof(tmp_dirent.d_name));
191                }
192                else
193                    is_first_entry = false;
194
195                /*
196                 * If the entry number or the check sum do not match
197                 * forget this series of long directory entries. These could
198                 * be orphaned entries depending on the history of the
199                 * disk.
200                 */
201                if ((lfn_entries != (*MSDOS_DIR_ENTRY_TYPE(entry) &
202                                     MSDOS_LAST_LONG_ENTRY_MASK)) ||
203                    (lfn_checksum != *MSDOS_DIR_LFN_CHECKSUM(entry)))
204                {
205                    lfn_start = FAT_FILE_SHORT_NAME;
206                    continue;
207                }
208
209                /*
210                 * Extract the file name into the directory entry. The data is
211                 * stored in UNICODE characters (16bit). No translation is
212                 * done for the possibly partial entry.
213                 * Once all entries have been assembled to a UTF-16 file name,
214                 * this file name will get converted to UTF-8.
215                 *
216                 * The DOS maximum length is 255 characters without the
217                 * trailing nul character. We need to range check the length to
218                 * fit in the directory entry name field.
219                 */
220
221                lfn_entries--;
222                offset_lfn = lfn_entries * MSDOS_LFN_LEN_PER_ENTRY;
223                tmp_lfn_len += msdos_get_utf16_string_from_long_entry (
224                  entry,
225                  &lfn_buf[offset_lfn],
226                  buf_size - offset_lfn,
227                  is_first_entry
228                );
229            }
230            else
231            {
232                fat_dir_pos_t dir_pos;
233
234                /*
235                 * Skip active entries until get the entry to start from.
236                 */
237                if (start)
238                {
239                    lfn_start = FAT_FILE_SHORT_NAME;
240                    start--;
241                    continue;
242                }
243
244                /*
245                 * Move the entry to the return buffer
246                 *
247                 * unfortunately there is no method to extract ino except to
248                 * open fat-file descriptor :( ... so, open it
249                 */
250
251                /* get number of cluster we are working with */
252                rc = fat_file_ioctl(&fs_info->fat, fat_fd, F_CLU_NUM,
253                                    j * bts2rd, &cur_cln);
254                if (rc != RC_OK)
255                {
256                    rtems_semaphore_release(fs_info->vol_sema);
257                    return rc;
258                }
259
260                fat_dir_pos_init(&dir_pos);
261                dir_pos.sname.cln = cur_cln;
262                dir_pos.sname.ofs = i;
263                rc = fat_file_open(&fs_info->fat, &dir_pos, &tmp_fat_fd);
264                if (rc != RC_OK)
265                {
266                    rtems_semaphore_release(fs_info->vol_sema);
267                    return rc;
268                }
269
270                /* fill in dirent structure */
271                /* XXX: from what and in what d_off should be computed ?! */
272                tmp_dirent.d_off = start + cmpltd;
273                tmp_dirent.d_reclen = sizeof(struct dirent);
274                tmp_dirent.d_ino = tmp_fat_fd->ino;
275
276                /*
277                 * If a long file name check if the correct number of entries
278                 * have been found and if the checksum is correct and if it is
279                 * convertable to utf8 string.  If not return the short file
280                 * name.
281                 */
282                if (lfn_start != FAT_FILE_SHORT_NAME)
283                {
284                    if (lfn_entries ||
285                        lfn_checksum != msdos_lfn_checksum(entry))
286                        lfn_start = FAT_FILE_SHORT_NAME;
287
288                    eno = (*convert_handler->utf16_to_utf8) (
289                        converter,
290                        lfn_buf,
291                        tmp_lfn_len,
292                        (uint8_t*)(&tmp_dirent.d_name[0]),
293                        &string_size);
294                    if (eno == 0) {
295                      tmp_dirent.d_namlen                    = string_size;
296                      tmp_dirent.d_name[tmp_dirent.d_namlen] = '\0';
297                    }
298                    else {
299                        lfn_start = FAT_FILE_SHORT_NAME;
300                    }
301                }
302
303                if (lfn_start == FAT_FILE_SHORT_NAME)
304                {
305                    /*
306                     * convert dir entry from fixed 8+3 format (without dot)
307                     * to 0..8 + 1dot + 0..3 format
308                     */
309                    tmp_dirent.d_namlen = msdos_format_dirent_with_dot(
310                        sfn_buf, entry); /* src text */
311                    eno = (*convert_handler->codepage_to_utf8) (
312                        converter,
313                        sfn_buf,
314                        tmp_dirent.d_namlen,
315                        (uint8_t*)(&tmp_dirent.d_name[0]),
316                        &string_size);
317                    if ( 0 == eno ) {
318                      tmp_dirent.d_namlen                    = string_size;
319                      tmp_dirent.d_name[tmp_dirent.d_namlen] = '\0';
320                    }
321                    else {
322                        cmpltd = -1;
323                        errno  = eno;
324                    }
325                }
326
327                if ( cmpltd >= 0 ) {
328                    memcpy(buffer + cmpltd, &tmp_dirent, sizeof(struct dirent));
329
330                    iop->offset = iop->offset + sizeof(struct dirent);
331                    cmpltd += (sizeof(struct dirent));
332                    count -= (sizeof(struct dirent));
333
334                    /* inode number extracted, close fat-file */
335                    rc = fat_file_close(&fs_info->fat, tmp_fat_fd);
336                    if (rc != RC_OK)
337                    {
338                        rtems_semaphore_release(fs_info->vol_sema);
339                        return rc;
340                    }
341                }
342            }
343
344            if (count <= 0)
345                break;
346        }
347        j++;
348    }
349
350    rtems_semaphore_release(fs_info->vol_sema);
351    return cmpltd;
352}
353
354/* msdos_dir_write --
355 *     no write for directory
356 */
357
358/* msdos_dir_stat --
359 *
360 * This routine will obtain the following information concerning the current
361 * directory:
362 *     st_dev      device id
363 *     st_ino      node serial number :)
364 *     st_mode     mode extracted from the node
365 *     st_size     total size in bytes
366 *     st_blksize  blocksize for filesystem I/O
367 *     st_blocks   number of blocks allocated
368 *     stat_mtime  time of last modification
369 *
370 * PARAMETERS:
371 *     loc - this directory
372 *     buf - stat buffer provided by user
373 *
374 * RETURNS:
375 *     RC_OK and filled stat buffer on success, or -1 if error occured (errno
376 *     set apropriately).
377 */
378int
379msdos_dir_stat(
380    const rtems_filesystem_location_info_t *loc,
381    struct stat *buf
382)
383{
384    rtems_status_code  sc = RTEMS_SUCCESSFUL;
385    msdos_fs_info_t   *fs_info = loc->mt_entry->fs_info;
386    fat_file_fd_t     *fat_fd = loc->node_access;
387
388    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
389                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
390    if (sc != RTEMS_SUCCESSFUL)
391        rtems_set_errno_and_return_minus_one(EIO);
392
393    buf->st_dev = rtems_disk_get_device_identifier(fs_info->fat.vol.dd);
394    buf->st_ino = fat_fd->ino;
395    buf->st_mode  = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
396    buf->st_rdev = 0ll;
397    buf->st_size = fat_fd->fat_file_size;
398    buf->st_blocks = fat_fd->fat_file_size >> FAT_SECTOR512_BITS;
399    buf->st_blksize = fs_info->fat.vol.bps;
400    buf->st_atime = fat_fd->mtime;
401    buf->st_ctime = fat_fd->ctime;
402    buf->st_mtime = fat_fd->mtime;
403
404    rtems_semaphore_release(fs_info->vol_sema);
405    return RC_OK;
406}
407
408/* msdos_dir_truncate --
409 *     No truncate for directory.
410 *
411 * PARAMETERS:
412 *
413 * RETURNS:
414 *
415 */
Note: See TracBrowser for help on using the repository browser.