2

I would like to convert a full path to a relative path based on a given directory, kinda like this

realpath -m /home/mobile_c/relpath/CCR/Headers/gc.h --relative-to=/home/mobile_c/relpath/CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h

except coded in C but im not sure how i would do so

my original intended result is from x file, resolved to x file

so it would expected to be, for example:

from ~/CCR/Headers/gc.h

to ~/CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h

relative_path (from > to) = ../UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h

relative_path (to > from) = ../../../../Headers/gc.h

this is what i currently have

#define ps(x) printf("%s = %s\n", #x, x)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <libgen.h>

void pathCanonicalize (char *path)
{
    size_t i;
    size_t j;
    size_t k;

    //Move to the beginning of the string
    i = 0;
    k = 0;

    //Replace backslashes with forward slashes
    while (path[i] != '\0') {
        //Forward slash or backslash separator found?
        if (path[i] == '/' || path[i] == '\\') {
            path[k++] = '/';
            while (path[i] == '/' || path[i] == '\\')
                i++;
        } else {
            path[k++] = path[i++];
        }
    }

    //Properly terminate the string with a NULL character
    path[k] = '\0';

    //Move back to the beginning of the string
    i = 0;
    j = 0;
    k = 0;

    //Parse the entire string
    do {
        //Forward slash separator found?
        if (path[i] == '/' || path[i] == '\0') {
            //"." element found?
            if ((i - j) == 1 && !strncmp (path + j, ".", 1)) {
                //Check whether the pathname is empty?
                if (k == 0) {
                    if (path[i] == '\0') {
                        path[k++] = '.';
                    } else if (path[i] == '/' && path[i + 1] == '\0') {
                        path[k++] = '.';
                        path[k++] = '/';
                    }
                } else if (k > 1) {
                    //Remove the final slash if necessary
                    if (path[i] == '\0')
                        k--;
                }
            }
            //".." element found?
            else if ((i - j) == 2 && !strncmp (path + j, "..", 2)) {
                //Check whether the pathname is empty?
                if (k == 0) {
                    path[k++] = '.';
                    path[k++] = '.';

                    //Append a slash if necessary
                    if (path[i] == '/')
                        path[k++] = '/';
                } else if (k > 1) {
                    //Search the path for the previous slash
                    for (j = 1; j < k; j++) {
                        if (path[k - j - 1] == '/')
                            break;
                    }

                    //Slash separator found?
                    if (j < k) {
                        if (!strncmp (path + k - j, "..", 2)) {
                            path[k++] = '.';
                            path[k++] = '.';
                        } else {
                            k = k - j - 1;
                        }

                        //Append a slash if necessary
                        if (k == 0 && path[0] == '/')
                            path[k++] = '/';
                        else if (path[i] == '/')
                            path[k++] = '/';
                    }
                    //No slash separator found?
                    else {
                        if (k == 3 && !strncmp (path, "..", 2)) {
                            path[k++] = '.';
                            path[k++] = '.';

                            //Append a slash if necessary
                            if (path[i] == '/')
                                path[k++] = '/';
                        } else if (path[i] == '\0') {
                            k = 0;
                            path[k++] = '.';
                        } else if (path[i] == '/' && path[i + 1] == '\0') {
                            k = 0;
                            path[k++] = '.';
                            path[k++] = '/';
                        } else {
                            k = 0;
                        }
                    }
                }
            } else {
                //Copy directory name
                memmove (path + k, path + j, i - j);
                //Advance write pointer
                k += i - j;

                //Append a slash if necessary
                if (path[i] == '/')
                    path[k++] = '/';
            }

            //Move to the next token
            while (path[i] == '/')
                i++;
            j = i;
        }
        else if (k == 0) {
            while (path[i] == '.' || path[i] == '/') {
                j++,i++;
            }
        }
    } while (path[i++] != '\0');

    //Properly terminate the string with a NULL character
    path[k] = '\0';
}

char * fullpath(char * path) {
    char * buf = malloc(4096);
    if (path[0] != '/') {
        getcwd(buf, 4096);
        strcat(buf, "/");
        strcat(buf, path);
        pathCanonicalize(buf);
        return buf;
    }
    else {
        strcpy(buf, path);
        return buf;
    }
}

char * resolve(char * from, char * to) {
    printf("resolving from \"%s\" to \"%s\"\n", from, to);
    puts("first, convert all paths to absolute paths");
    from = fullpath(from);
    to = fullpath(to);
    ps(from);
    ps(to);
    puts("next, we attempt to convert to relative path");
    free(from);
    free(to);
    return NULL;
}

int main ( int argc, char *argv[] )
{
    char * to = "./CCR/Headers/gc.h";
    char * from = "./CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h";
    resolve(to,from);
    resolve(from,to);
    return 0;
}

and the output:

mobile_c@Mobile-C:~/relpath$ gcc ./rel.c && ./a.out
resolving from "./CCR/Headers/gc.h" to "./CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h"
first, convert all paths to absolute paths
from = /home/mobile_c/relpath/CCR/Headers/gc.h
to = /home/mobile_c/relpath/CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h
next, we attempt to convert to relative path
resolving from "./CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h" to "./CCR/Headers/gc.h"
first, convert all paths to absolute paths
from = /home/mobile_c/relpath/CCR/UserFiles/ccr_resources/Scripts/Garbage_Collector/gc.h
to = /home/mobile_c/relpath/CCR/Headers/gc.h
next, we attempt to convert to relative path
mobile_c@Mobile-C:~/relpath$ 
PSP CODER
  • 93
  • 10
  • Please provide the code you got so far with an [mcve]. It would help us to help you. – izlin Aug 29 '18 at 08:48
  • 4
    I don't think there's a standard function for going in this direction, so basically you have to come up with your own algorithm to do it. Finding the common prefix and replacing each directory in it with `"../"` seems to come close. – unwind Aug 29 '18 at 08:48
  • Duplicates : [How to find relative path given two absolute paths?](https://stackoverflow.com/questions/29055511/how-to-find-relative-path-given-two-absolute-paths), [How to convert absolute path to relative in c linux](https://stackoverflow.com/questions/9663845/how-to-convert-absolute-path-to-relative-in-c-linux) – Sander De Dycker Aug 29 '18 at 08:53
  • You write _"If possible I would like this to work even if given paths and resolved path does not exist."_: of course it is possible, the problem can be resolved purely by string manipulation as uwind already has suggested. – Jabberwocky Aug 29 '18 at 08:54
  • potentially interesting : [Convert absolute path into relative path given a current directory using Bash](https://stackoverflow.com/questions/2564634/convert-absolute-path-into-relative-path-given-a-current-directory-using-bash) – Sander De Dycker Aug 29 '18 at 08:54
  • The shell command realpath could also be used with `realpath --relative-to= ` -> returns the relatice path – izlin Aug 29 '18 at 08:58
  • 1
    The problem boils down to the *longest common prefix terminated by* `'/'`. One approach would be to take `fullpath` and loop with `strrchr` looking for `'/'` as your character, use difference between the returned pointer and `fullpath` and call `strncmp` on both until it is satisfied or the return of `strrchr` equals `fullpath` or is `NULL`. – David C. Rankin Aug 29 '18 at 09:01
  • updated post with use and example – PSP CODER Aug 29 '18 at 11:13

0 Answers0