basename alternative

Hi

I wrote this code to ensure I am using a safer basename:
- not modifying argument
- not using malloc to avoid free
- I do not need it thread safe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char *BaseName(const char* full_path) {
    static char ret[PATH_MAX];
    char path_copy[PATH_MAX];
    char *ptr;

    if (full_path == NULL || strcmp(full_path, "") == 0) {
        strcpy(ret, ".");
    } else {
        strcpy(path_copy, full_path);
        ptr = strtok(path_copy, "/");
        if (ptr == NULL)
            strcpy(ret, "/");

        while (ptr != NULL) {
            strcpy(ret, ptr);
            ptr = strtok(NULL, "/");
        }
    }

    ptr = ret;
    return ptr;
}


I tested all exceptions and it seems to return same values as basename
It also supports spaces, unlike original basename

Any issue with this code? I chose static instead of malloc a pointer on start to make it simpler since it is not thread safe anyway
Any issue with this code?
Yes, use of a static.

You're not using bounded copies; i.e. you should be using strncpy and strncat.

Also, you should be using strtok_r rather strtok to support reentrancy.

I chose static instead of malloc a pointer on start to make it simpler since it is not thread safe anyway
But you've also made it non-reentrant.

It's the use of the static buffer and strtok that's stopping it from being reentrant and thread safe.
Last edited on
thanks
I wrote it for my needs, and mentioned in OP that I do not need it reetrant
I will change to malloc a pointer on first call though

About strncpy, in my case, PATH_MAX is 4096. However, sure it is safer to use strncpy

Thank you for the feedback

By the way, I saw some basename/dirname implementations using xmalloc on every call, without even a free

I found no info on the implementation used by Android NDK. Any idea if they also have a broken basename/dirname modifying argument and maybe not freeing resources?
Just for the feedback
I found an older NDK implementation of basename_r and dirname_r

I fixed dirname_r to never alter argument by setting the *startp pointer
Here's the fixed code that seems safe.

I added a small main() that prints out result from system basename/dirname, in-code functions and original argument passed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
int basename_r(const char* path, char*  buffer, size_t  bufflen)
{
    const char *endp, *startp;
    int         len, result;

    /* Empty or NULL string gets treated as "." */
    if (path == NULL || *path == '\0') {
        startp  = ".";
        len     = 1;
        goto Exit;
    }

    /* Strip trailing slashes */
    endp = path + strlen(path) - 1;
    while (endp > path && *endp == '/')
        endp--;

    /* All slashes becomes "/" */
    if (endp == path && *endp == '/') {
        startp = "/";
        len    = 1;
        goto Exit;
    }

    /* Find the start of the base */
    startp = endp;
    while (startp > path && *(startp - 1) != '/')
        startp--;

    len = endp - startp +1;

Exit:
    result = len;
    if (buffer == NULL) {
        return result;
    }
    if (len > (int)bufflen-1) {
        len    = (int)bufflen-1;
        result = -1;
        errno  = ERANGE;
    }

    if (len >= 0) {
        memcpy( buffer, startp, len );
        buffer[len] = 0;
    }
    return result;
}

char* BaseName(const char*  path) {
    static char* bname = NULL;
    int ret;

    if (bname == NULL) {
        bname = (char *)malloc(PATH_MAX);
        if (bname == NULL)
            return(NULL);
    }
    ret = basename_r(path, bname, PATH_MAX);
    return (ret < 0) ? NULL : bname;
}

int
dirname_r(const char*  path, char*  buffer, size_t  bufflen)
{
    const char *endp, *startp;
    int         result, len;

    /* Empty or NULL string gets treated as "." */
    if (path == NULL || *path == '\0') {
        startp = ".";
        len  = 1;
        goto Exit;
    }

    /* Strip trailing slashes */
    endp = path + strlen(path) - 1;
    while (endp > path && *endp == '/')
        endp--;

    /* Find the start of the dir */
    while (endp > path && *endp != '/')
        endp--;

    /* Either the dir is "/" or there are no slashes */
    if (endp == path) {
        startp = (*endp == '/') ? "/" : ".";
        len  = 1;
        goto Exit;
    }

    do {
        endp--;
    } while (endp > path && *endp == '/');

    startp = path;
    len = endp - startp +1;

Exit:
    result = len;
    if (len+1 > PATH_MAX) {
        errno = ENAMETOOLONG;
        return -1;
    }
    if (buffer == NULL)
        return result;

    if (len > (int)bufflen-1) {
        len    = (int)bufflen-1;
        result = -1;
        errno  = ERANGE;
    }

    if (len >= 0) {
        memcpy( buffer, startp, len );
        buffer[len] = 0;
    }
    return result;
}

char* DirName(const char*  path) {
    static char*  bname = NULL;
    int           ret;

    if (bname == NULL) {
        bname = (char *)malloc(PATH_MAX);
        if (bname == NULL)
            return(NULL);
    }

    ret = dirname_r(path, bname, PATH_MAX);
    return (ret < 0) ? NULL : bname;
}

int main(int argc, char **argv) {
    if (argc != 2)
        printf("usage: bin [path/to/name]\n");
    else {
        printf("basename=%s\n", BaseName(argv[1]));
        char cmd[PATH_MAX];
        sprintf(cmd, "basename %s", argv[1]);
        system(cmd);

        printf("dirname=%s\n", DirName(argv[1]));
        sprintf(cmd, "dirname %s", argv[1]);
        system(cmd);
        printf("original:%s\n", argv[1]);
    }
    return 0;
}
Topic archived. No new replies allowed.