Subversion Repositories HelenOS

Compare Revisions

Ignore whitespace Rev 2752 → Rev 2753

/trunk/uspace/lib/libc/include/vfs/canonify.h
0,0 → 1,43
/*
* Copyright (c) 2008 Jakub Jermar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
 
/** @addtogroup libc
* @{
*/
/** @file
*/
 
#ifndef LIBC_VFS_CANONIFY_H_
#define LIBC_VFS_CANONIFY_H_
 
extern char *canonify(char *, size_t *);
 
#endif
 
/** @}
*/
/trunk/uspace/lib/libc/generic/vfs.c
File deleted
/trunk/uspace/lib/libc/generic/vfs/vfs.c
0,0 → 1,382
/*
* Copyright (c) 2008 Jakub Jermar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
 
/** @addtogroup libc
* @{
*/
/** @file
*/
#include <vfs.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ipc/ipc.h>
#include <ipc/services.h>
#include <async.h>
#include <atomic.h>
#include <futex.h>
#include <errno.h>
#include <string.h>
#include "../../srv/vfs/vfs.h"
 
int vfs_phone = -1;
atomic_t vfs_phone_futex = FUTEX_INITIALIZER;
 
static int vfs_connect(void)
{
if (vfs_phone < 0)
vfs_phone = ipc_connect_me_to(PHONE_NS, SERVICE_VFS, 0, 0);
return vfs_phone;
}
 
int mount(const char *fs_name, const char *mp, const char *dev)
{
int res;
ipcarg_t rc;
aid_t req;
 
int dev_handle = 0; /* TODO */
 
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
req = async_send_1(vfs_phone, VFS_MOUNT, dev_handle, NULL);
rc = ipc_data_write_start(vfs_phone, (void *)fs_name, strlen(fs_name));
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
rc = ipc_data_write_start(vfs_phone, (void *)mp, strlen(mp));
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
async_wait_for(req, &rc);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
 
static int _open(const char *path, int lflag, int oflag, ...)
{
int res;
ipcarg_t rc;
ipc_call_t answer;
aid_t req;
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
req = async_send_3(vfs_phone, VFS_OPEN, lflag, oflag, 0, &answer);
rc = ipc_data_write_start(vfs_phone, path, strlen(path));
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
async_wait_for(req, &rc);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) IPC_GET_ARG1(answer);
}
 
int open(const char *path, int oflag, ...)
{
return _open(path, L_FILE, oflag);
}
 
int close(int fildes)
{
int res;
ipcarg_t rc;
 
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
rc = async_req_1_0(vfs_phone, VFS_CLOSE, fildes);
 
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int)rc;
}
 
ssize_t read(int fildes, void *buf, size_t nbyte)
{
int res;
ipcarg_t rc;
ipc_call_t answer;
aid_t req;
 
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
req = async_send_1(vfs_phone, VFS_READ, fildes, &answer);
rc = ipc_data_read_start(vfs_phone, (void *)buf, nbyte);
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (ssize_t) rc;
}
async_wait_for(req, &rc);
async_serialize_end();
futex_up(&vfs_phone_futex);
if (rc == EOK)
return (ssize_t) IPC_GET_ARG1(answer);
else
return -1;
}
 
ssize_t write(int fildes, const void *buf, size_t nbyte)
{
int res;
ipcarg_t rc;
ipc_call_t answer;
aid_t req;
 
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
req = async_send_1(vfs_phone, VFS_WRITE, fildes, &answer);
rc = ipc_data_write_start(vfs_phone, (void *)buf, nbyte);
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (ssize_t) rc;
}
async_wait_for(req, &rc);
async_serialize_end();
futex_up(&vfs_phone_futex);
if (rc == EOK)
return (ssize_t) IPC_GET_ARG1(answer);
else
return -1;
}
 
off_t lseek(int fildes, off_t offset, int whence)
{
int res;
ipcarg_t rc;
 
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
off_t newoffs;
rc = async_req_3_1(vfs_phone, VFS_SEEK, fildes, offset, whence,
(ipcarg_t)&newoffs);
 
async_serialize_end();
futex_up(&vfs_phone_futex);
 
if (rc != EOK)
return (off_t) -1;
return newoffs;
}
 
int ftruncate(int fildes, off_t length)
{
int res;
ipcarg_t rc;
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
rc = async_req_2_0(vfs_phone, VFS_TRUNCATE, fildes, length);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
 
DIR *opendir(const char *dirname)
{
DIR *dirp = malloc(sizeof(DIR));
if (!dirp)
return NULL;
dirp->fd = _open(dirname, L_DIRECTORY, 0);
if (dirp->fd < 0) {
free(dirp);
return NULL;
}
return dirp;
}
 
struct dirent *readdir(DIR *dirp)
{
ssize_t len = read(dirp->fd, &dirp->res.d_name[0], NAME_MAX + 1);
if (len <= 0)
return NULL;
return &dirp->res;
}
 
void rewinddir(DIR *dirp)
{
(void) lseek(dirp->fd, 0, SEEK_SET);
}
 
int closedir(DIR *dirp)
{
(void) close(dirp->fd);
free(dirp);
return 0;
}
 
int mkdir(const char *path, mode_t mode)
{
int res;
ipcarg_t rc;
aid_t req;
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
req = async_send_1(vfs_phone, VFS_MKDIR, mode, NULL);
rc = ipc_data_write_start(vfs_phone, path, strlen(path));
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
async_wait_for(req, &rc);
async_serialize_end();
futex_up(&vfs_phone_futex);
return EOK;
}
 
static int _unlink(const char *path, int lflag)
{
int res;
ipcarg_t rc;
aid_t req;
futex_down(&vfs_phone_futex);
async_serialize_start();
if (vfs_phone < 0) {
res = vfs_connect();
if (res < 0) {
async_serialize_end();
futex_up(&vfs_phone_futex);
return res;
}
}
req = async_send_0(vfs_phone, VFS_UNLINK, NULL);
rc = ipc_data_write_start(vfs_phone, path, strlen(path));
if (rc != EOK) {
async_wait_for(req, NULL);
async_serialize_end();
futex_up(&vfs_phone_futex);
return (int) rc;
}
async_wait_for(req, &rc);
async_serialize_end();
futex_up(&vfs_phone_futex);
return EOK;
}
 
int unlink(const char *path)
{
return _unlink(path, L_NONE);
}
 
int rmdir(const char *path)
{
return _unlink(path, L_DIRECTORY);
}
 
/** @}
*/
/trunk/uspace/lib/libc/generic/vfs/canonify.c
0,0 → 1,339
/*
* Copyright (c) 2008 Jakub Jermar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
 
/** @addtogroup libc
* @{
*/
 
/**
* @file
* @brief
*/
 
#include <stdlib.h>
 
/** Token types used for tokenization of path. */
typedef enum {
TK_INVALID,
TK_SLASH,
TK_DOT,
TK_DOTDOT,
TK_COMP,
TK_NUL
} tokval_t;
 
typedef struct {
tokval_t kind;
char *start;
char *stop;
} token_t;
 
/** Fake up the TK_SLASH token. */
static token_t slash_token(char *start)
{
token_t ret;
ret.kind = TK_SLASH;
ret.start = start;
ret.stop = start;
return ret;
}
 
/** Given a token, return the next token. */
static token_t next_token(token_t *cur)
{
token_t ret;
 
if (cur->stop[1] == '\0') {
ret.kind = TK_NUL;
ret.start = cur->stop + 1;
ret.stop = ret.start;
return ret;
}
if (cur->stop[1] == '/') {
ret.kind = TK_SLASH;
ret.start = cur->stop + 1;
ret.stop = ret.start;
return ret;
}
if (cur->stop[1] == '.' && (!cur->stop[2] || cur->stop[2] == '/')) {
ret.kind = TK_DOT;
ret.start = cur->stop + 1;
ret.stop = ret.start;
return ret;
}
if (cur->stop[1] == '.' && cur->stop[2] == '.' &&
(!cur->stop[3] || cur->stop[3] == '/')) {
ret.kind = TK_DOTDOT;
ret.start = cur->stop + 1;
ret.stop = cur->stop + 2;
return ret;
}
unsigned i;
for (i = 1; cur->stop[i] && cur->stop[i] != '/'; i++)
;
ret.kind = TK_COMP;
ret.start = &cur->stop[1];
ret.stop = &cur->stop[i - 1];
return ret;
}
 
/** States used by canonify(). */
typedef enum {
S_INI,
S_A,
S_B,
S_C,
S_ACCEPT,
S_RESTART,
S_REJECT
} state_t;
 
typedef struct {
state_t s;
void (* f)(token_t *, token_t *, token_t *);
} change_state_t;
 
/*
* Actions that can be performed when transitioning from one
* state of canonify() to another.
*/
static void set_first_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
*tfsl = *t;
}
static void save_component(token_t *t, token_t *tfsl, token_t *tlcomp)
{
*tlcomp = *t;
}
static void terminate_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
if (tfsl->stop[1]) /* avoid writing to a well-formatted path */
tfsl->stop[1] = '\0';
}
static void remove_trailing_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
t->start[-1] = '\0';
}
/** Eat the extra '/'..
*
* @param t The current TK_SLASH token.
*/
static void shift_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
char *p = t->start;
char *q = t->stop + 1;
while ((*p++ = *q++))
;
}
/** Eat the extra '.'.
*
* @param t The current TK_DOT token.
*/
static void shift_dot(token_t *t, token_t *tfsl, token_t *tlcomp)
{
char *p = t->start;
char *q = t->stop + 1;
while ((*p++ = *q++))
;
}
/** Collapse the TK_COMP TK_SLASH TK_DOTDOT pattern.
*
* @param t The current TK_DOTDOT token.
* @param tlcomp The last TK_COMP token.
*/
static void shift_dotdot(token_t *t, token_t *tfsl, token_t *tlcomp)
{
char *p = tlcomp->start;
char *q = t->stop + 1;
while ((*p++ = *q++))
;
}
 
/** Transition function for canonify(). */
static change_state_t trans[4][6] = {
[S_INI] = {
[TK_SLASH] = {
.s = S_A,
.f = set_first_slash,
},
[TK_DOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_DOTDOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_COMP] = {
.s = S_REJECT,
.f = NULL,
},
[TK_NUL] = {
.s = S_REJECT,
.f = NULL,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
},
[S_A] = {
[TK_SLASH] = {
.s = S_A,
.f = set_first_slash,
},
[TK_DOT] = {
.s = S_A,
.f = NULL,
},
[TK_DOTDOT] = {
.s = S_A,
.f = NULL,
},
[TK_COMP] = {
.s = S_B,
.f = save_component,
},
[TK_NUL] = {
.s = S_ACCEPT,
.f = terminate_slash,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
},
[S_B] = {
[TK_SLASH] = {
.s = S_C,
.f = NULL,
},
[TK_DOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_DOTDOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_COMP] = {
.s = S_REJECT,
.f = NULL,
},
[TK_NUL] = {
.s = S_ACCEPT,
.f = NULL,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
},
[S_C] = {
[TK_SLASH] = {
.s = S_RESTART,
.f = shift_slash,
},
[TK_DOT] = {
.s = S_RESTART,
.f = shift_dot,
},
[TK_DOTDOT] = {
.s = S_RESTART,
.f = shift_dotdot,
},
[TK_COMP] = {
.s = S_B,
.f = save_component,
},
[TK_NUL] = {
.s = S_ACCEPT,
.f = remove_trailing_slash,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
}
};
 
/** Canonify a file system path.
*
* A file system path is canonical, if the following holds:
* 1) the path is absolute (i.e. a/b/c is not canonical)
* 2) there is no trailing slash in the path (i.e. /a/b/c is not canonical)
* 3) there is no extra slash in the path (i.e. /a//b/c is not canonical)
* 4) there is no '.' component in the path (i.e. /a/./b/c is not canonical)
* 5) there is no '..' component in the path (i.e. /a/b/../c is not canonical)
*
* This function makes a potentially non-canonical file system path canonical.
* It works in-place and requires a NULL-terminated input string.
*
* @param path Path to be canonified.
* @param lenp Pointer where the length of the final path will be
* stored. Can be NULL.
*
* @return Canonified path or NULL on failure.
*/
char *canonify(char *path, size_t *lenp)
{
state_t state;
token_t t;
token_t tfsl; /* first slash */
token_t tlcomp; /* last component */
if (*path != '/')
return NULL;
tfsl = slash_token(path);
restart:
state = S_INI;
t = tfsl;
tlcomp = tfsl;
while (state != S_ACCEPT && state != S_RESTART && state != S_REJECT) {
if (trans[state][t.kind].f)
trans[state][t.kind].f(&t, &tfsl, &tlcomp);
state = trans[state][t.kind].s;
t = next_token(&t);
}
switch (state) {
case S_RESTART:
goto restart;
case S_REJECT:
return NULL;
case S_ACCEPT:
if (lenp)
*lenp = (size_t)((tlcomp.stop - tfsl.start) + 1);
return tfsl.start;
default:
abort();
}
}
 
/**
* @}
*/
/trunk/uspace/lib/libc/Makefile
74,7 → 74,8
generic/err.c \
generic/stdlib.c \
generic/mman.c \
generic/vfs.c
generic/vfs/vfs.c \
generic/vfs/canonify.c
 
ARCH_SOURCES += \
arch/$(ARCH)/src/entry.s \
/trunk/uspace/srv/vfs/vfs_lookup.c
35,22 → 35,19
* @brief
*/
 
#include "vfs.h"
#include <ipc/ipc.h>
#include <async.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <bool.h>
#include <futex.h>
#include <libadt/list.h>
#include <atomic.h>
#include "vfs.h"
#include <vfs/canonify.h>
 
#define min(a, b) ((a) < (b) ? (a) : (b))
 
/* Forward static declarations. */
static char *canonify(char *path, size_t *lenp);
 
atomic_t plb_futex = FUTEX_INITIALIZER;
link_t plb_head; /**< PLB entry ring buffer. */
uint8_t *plb = NULL;
180,303 → 177,6
return rc;
}
 
/** Token types used for tokenization of path. */
typedef enum {
TK_INVALID,
TK_SLASH,
TK_DOT,
TK_DOTDOT,
TK_COMP,
TK_NUL
} tokval_t;
 
typedef struct {
tokval_t kind;
char *start;
char *stop;
} token_t;
 
/** Fake up the TK_SLASH token. */
static token_t slash_token(char *start)
{
token_t ret;
ret.kind = TK_SLASH;
ret.start = start;
ret.stop = start;
return ret;
}
 
/** Given a token, return the next token. */
static token_t next_token(token_t *cur)
{
token_t ret;
 
if (cur->stop[1] == '\0') {
ret.kind = TK_NUL;
ret.start = cur->stop + 1;
ret.stop = ret.start;
return ret;
}
if (cur->stop[1] == '/') {
ret.kind = TK_SLASH;
ret.start = cur->stop + 1;
ret.stop = ret.start;
return ret;
}
if (cur->stop[1] == '.' && (!cur->stop[2] || cur->stop[2] == '/')) {
ret.kind = TK_DOT;
ret.start = cur->stop + 1;
ret.stop = ret.start;
return ret;
}
if (cur->stop[1] == '.' && cur->stop[2] == '.' &&
(!cur->stop[3] || cur->stop[3] == '/')) {
ret.kind = TK_DOTDOT;
ret.start = cur->stop + 1;
ret.stop = cur->stop + 2;
return ret;
}
unsigned i;
for (i = 1; cur->stop[i] && cur->stop[i] != '/'; i++)
;
ret.kind = TK_COMP;
ret.start = &cur->stop[1];
ret.stop = &cur->stop[i - 1];
return ret;
}
 
/** States used by canonify(). */
typedef enum {
S_INI,
S_A,
S_B,
S_C,
S_ACCEPT,
S_RESTART,
S_REJECT
} state_t;
 
typedef struct {
state_t s;
void (* f)(token_t *, token_t *, token_t *);
} change_state_t;
 
/*
* Actions that can be performed when transitioning from one
* state of canonify() to another.
*/
static void set_first_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
*tfsl = *t;
}
static void save_component(token_t *t, token_t *tfsl, token_t *tlcomp)
{
*tlcomp = *t;
}
static void terminate_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
if (tfsl->stop[1]) /* avoid writing to a well-formatted path */
tfsl->stop[1] = '\0';
}
static void remove_trailing_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
t->start[-1] = '\0';
}
/** Eat the extra '/'..
*
* @param t The current TK_SLASH token.
*/
static void shift_slash(token_t *t, token_t *tfsl, token_t *tlcomp)
{
char *p = t->start;
char *q = t->stop + 1;
while ((*p++ = *q++))
;
}
/** Eat the extra '.'.
*
* @param t The current TK_DOT token.
*/
static void shift_dot(token_t *t, token_t *tfsl, token_t *tlcomp)
{
char *p = t->start;
char *q = t->stop + 1;
while ((*p++ = *q++))
;
}
/** Collapse the TK_COMP TK_SLASH TK_DOTDOT pattern.
*
* @param t The current TK_DOTDOT token.
* @param tlcomp The last TK_COMP token.
*/
static void shift_dotdot(token_t *t, token_t *tfsl, token_t *tlcomp)
{
char *p = tlcomp->start;
char *q = t->stop + 1;
while ((*p++ = *q++))
;
}
 
/** Transition function for canonify(). */
static change_state_t trans[4][6] = {
[S_INI] = {
[TK_SLASH] = {
.s = S_A,
.f = set_first_slash,
},
[TK_DOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_DOTDOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_COMP] = {
.s = S_REJECT,
.f = NULL,
},
[TK_NUL] = {
.s = S_REJECT,
.f = NULL,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
},
[S_A] = {
[TK_SLASH] = {
.s = S_A,
.f = set_first_slash,
},
[TK_DOT] = {
.s = S_A,
.f = NULL,
},
[TK_DOTDOT] = {
.s = S_A,
.f = NULL,
},
[TK_COMP] = {
.s = S_B,
.f = save_component,
},
[TK_NUL] = {
.s = S_ACCEPT,
.f = terminate_slash,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
},
[S_B] = {
[TK_SLASH] = {
.s = S_C,
.f = NULL,
},
[TK_DOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_DOTDOT] = {
.s = S_REJECT,
.f = NULL,
},
[TK_COMP] = {
.s = S_REJECT,
.f = NULL,
},
[TK_NUL] = {
.s = S_ACCEPT,
.f = NULL,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
},
[S_C] = {
[TK_SLASH] = {
.s = S_RESTART,
.f = shift_slash,
},
[TK_DOT] = {
.s = S_RESTART,
.f = shift_dot,
},
[TK_DOTDOT] = {
.s = S_RESTART,
.f = shift_dotdot,
},
[TK_COMP] = {
.s = S_B,
.f = save_component,
},
[TK_NUL] = {
.s = S_ACCEPT,
.f = remove_trailing_slash,
},
[TK_INVALID] = {
.s = S_REJECT,
.f = NULL,
},
}
};
 
/** Canonify a file system path.
*
* A file system path is canonical, if the following holds:
* 1) the path is absolute (i.e. a/b/c is not canonical)
* 2) there is no trailing slash in the path (i.e. /a/b/c is not canonical)
* 3) there is no extra slash in the path (i.e. /a//b/c is not canonical)
* 4) there is no '.' component in the path (i.e. /a/./b/c is not canonical)
* 5) there is no '..' component in the path (i.e. /a/b/../c is not canonical)
*
* This function makes a potentially non-canonical file system path canonical.
* It works in-place and requires a NULL-terminated input string.
*
* @param path Path to be canonified.
* @param lenp Pointer where the length of the final path will be
* stored. Can be NULL.
*
* @return Canonified path or NULL on failure.
*/
char *canonify(char *path, size_t *lenp)
{
state_t state;
token_t t;
token_t tfsl; /* first slash */
token_t tlcomp; /* last component */
if (*path != '/')
return NULL;
tfsl = slash_token(path);
restart:
state = S_INI;
t = tfsl;
tlcomp = tfsl;
while (state != S_ACCEPT && state != S_RESTART && state != S_REJECT) {
if (trans[state][t.kind].f)
trans[state][t.kind].f(&t, &tfsl, &tlcomp);
state = trans[state][t.kind].s;
t = next_token(&t);
}
switch (state) {
case S_RESTART:
goto restart;
case S_REJECT:
return NULL;
case S_ACCEPT:
if (lenp)
*lenp = (size_t)((tlcomp.stop - tfsl.start) + 1);
return tfsl.start;
default:
abort();
}
}
 
/**
* @}
*/