• getOpsFromSelf() (Long Post)

    From Mike Sanders@21:1/5 to All on Tue Feb 20 06:56:06 2024
    Just thinking aloud... Imagining here allowing more advanced users of
    my app to modify a string of digits residing at the end of my project's binary/exe. When the app loads, it reads in those digits. So far (knock
    on wood), I've not run up against a single case where the code below has
    failed to work under multiple OSs. A method mostly for power user's.

    The user simply opens the compiled project in their favorite hex editor
    & modifies the pre-existing string, a pseudo bitmask of sorts. Certainly
    the user could unwittingly add/remove bytes rather than only modifying the bytes, still its a nifty idea. But practical? I'm not so sure on that point.

    Any other gotchas of note?

    int main(int argc, char *argv[]) {

    #ifdef WIN
    getOpsFromSelf(NULL);
    #else
    getOpsFromSelf(argv[0]);
    #endif

    // work

    return 0;

    }

    void getOpsFromSelf(char *fname) {

    #ifdef WIN
    char path[MAX_PATH];
    GetModuleFileName(NULL, path, MAX_PATH);
    #else
    char path[PATH_MAX];
    char *tmp = getpath(fname);
    strncpy(path, tmp, sizeof(path));
    path[sizeof(path) - 1] = '\0';
    free(tmp);
    #endif

    FILE *bin = fopen(path, "rb");
    if (bin == NULL) return; // return if file cant be opened

    fseek(bin, -3, SEEK_END); // jump 3 bytes before end of file
    unsigned char bytes[3];
    fread(bytes, 1, 3, bin);
    fclose(bin);

    // ensure last 3 bytes are digits, else return
    for (int i = 0; i < 2; i++) if (!isdigit(bytes[i])) return;

    // assign values from bytes
    ops.x = (bytes[0] == '1' || bytes[0] == '2') ? bytes[0] - '0' : 1; // 1-2
    ops.y = (bytes[1] == '1') ? bytes[1] - '0' : 0; // 0-1
    ops.z = (bytes[2] == '1') ? bytes[2] - '0' : 0; // 0-1

    }

    #ifdef NIX

    char *getpath(char *fname) {

    const char *PATH_SEPARATOR = ":"; // unix-like
    const char *DIR_SEPARATOR = "/"; // unix-like
    const int MAX_SIZE = 1024;

    struct stat buffer;

    if (stat(fname, &buffer) == 0) {
    char *result = strdup(fname);
    if (!result) return NULL; // memory allocation error <--
    return result;
    }

    char *path_env = getenv("PATH");
    if (!path_env) return NULL;

    char path_env_copy[MAX_SIZE];
    strncpy(path_env_copy, path_env, sizeof(path_env_copy));
    path_env_copy[sizeof(path_env_copy) - 1] = '\0';

    char full_path[MAX_SIZE];
    char *dir = strtok(path_env_copy, PATH_SEPARATOR);

    while (dir) {
    snprintf(full_path, sizeof(full_path), "%s%s%s", dir,
    (dir[strlen(dir) - 1] == DIR_SEPARATOR[0] ? "" :
    DIR_SEPARATOR), fname);
    if (stat(full_path, &buffer) == 0) {
    char *result = strdup(full_path);
    if (!result) return NULL; // memory allocation error <--
    return result;
    }
    dir = strtok(NULL, PATH_SEPARATOR);
    }

    return NULL;
    }

    #endif

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Spiros Bousbouras on Sat Feb 24 21:22:55 2024
    Spiros Bousbouras <[email protected]> wrote:

    Compared to other options like command line options or configuration files or environmental variables or asking the user interactively to set the values , this approach seems very much worse. It is a lot less practical *and* fragile.
    How can you know that this specific string will exist at a certain position in the executable ?

    Because it checks that does...

    // ensure last 3 bytes are digits, else return
    for (int i = 0; i < 2; i++) if (!isdigit(bytes[i])) return;

    What happens if getpath() returns NULL ?

    Ouch busted...

    You keep writing 3 .Best to use a symbolic constant.

    Why? 3 is always 3. Maybe I dont undestand what you mean...

    I take it ops is a global variable ?

    In this example, yes.

    On Unix , if you just want to test for the existence of a file , access()
    is a simpler way to do it.

    char *path_env = getenv("PATH");
    if (!path_env) return NULL;

    char path_env_copy[MAX_SIZE];
    strncpy(path_env_copy, path_env, sizeof(path_env_copy));
    path_env_copy[sizeof(path_env_copy) - 1] = '\0';

    char full_path[MAX_SIZE];
    char *dir = strtok(path_env_copy, PATH_SEPARATOR);

    while (dir) {
    snprintf(full_path, sizeof(full_path), "%s%s%s", dir,
    (dir[strlen(dir) - 1] == DIR_SEPARATOR[0] ? "" :
    DIR_SEPARATOR), fname);
    if (stat(full_path, &buffer) == 0) {
    char *result = strdup(full_path);
    if (!result) return NULL; // memory allocation error <--

    This line is redundant.

    Please explain why you think so.

    I wouldn't use strtok() at all for this but rather strchr() and keep track myself of the position in the string and not modify the string at
    all. This way

    - you can use the string returned by getenv() directly instead of having
    to copy it.

    - you don't have to call strlen() when you have already traversed the
    string to find the separator. Traversing the string for a second time
    probably won't have a noticeable effect for performance in this context
    but this kind of thing where the code has done a certain somewhat costly
    computation (traversing part of the string) and then immediately does
    again almost the same computation instead of using the result found by
    the first computation , grates me.

    I dunno, is it perfect? Nah. Is it a good start? Well, yeah =)

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Spiros Bousbouras on Sat Feb 24 21:25:56 2024
    Spiros Bousbouras <[email protected]> wrote:

    If I remeber correctly , the convention is that if :: exists in PATH
    then it's the same as :.: i.e. the current working directory. Your
    code does not handle this case. Yet another reason to do the searching
    in the string yourself instead of using strtok() .

    It does handle the current directory... before it traverses the users path.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ben Bacarisse@21:1/5 to Mike Sanders on Sun Feb 25 00:48:24 2024
    [email protected] (Mike Sanders) writes:

    Spiros Bousbouras <[email protected]> wrote:

    Compared to other options like command line options or configuration files or
    environmental variables or asking the user interactively to set the values , >> this approach seems very much worse. It is a lot less practical *and* fragile.
    How can you know that this specific string will exist at a certain position >> in the executable ?

    Because it checks that does...

    // ensure last 3 bytes are digits, else return
    for (int i = 0; i < 2; i++) if (!isdigit(bytes[i])) return;

    What happens if getpath() returns NULL ?

    Ouch busted...

    You keep writing 3 .Best to use a symbolic constant.

    Why? 3 is always 3. Maybe I dont undestand what you mean...

    And 3 != 2. Maybe using "CODE_LENGTH" everywhere would have prevented
    the (probable) bug above where the loop tests only two characters.
    After

    #define CODE_LENGTH 3

    would you have written

    for (int i = 0; i < CODE_LENGTH - 1; i++) ...

    ? I don't know, but I think you might have thought twice about that -1.

    I take it ops is a global variable ?

    In this example, yes.

    On Unix , if you just want to test for the existence of a file , access()
    is a simpler way to do it.

    char *path_env = getenv("PATH");
    if (!path_env) return NULL;

    char path_env_copy[MAX_SIZE];
    strncpy(path_env_copy, path_env, sizeof(path_env_copy));
    path_env_copy[sizeof(path_env_copy) - 1] = '\0';

    char full_path[MAX_SIZE];
    char *dir = strtok(path_env_copy, PATH_SEPARATOR);

    while (dir) {
    snprintf(full_path, sizeof(full_path), "%s%s%s", dir,
    (dir[strlen(dir) - 1] == DIR_SEPARATOR[0] ? "" :
    DIR_SEPARATOR), fname);
    if (stat(full_path, &buffer) == 0) {
    char *result = strdup(full_path);
    if (!result) return NULL; // memory allocation error <--

    This line is redundant.

    Please explain why you think so.

    Given the following line (return result;) there is no point in returning
    NULL is the special case where result is NULL since NULL will be
    returned when result is NULL. In fact, the whole three-line compound
    statement is (semantically) equivalent to

    return strdup(full_path);

    I wouldn't use strtok() at all for this but rather strchr() and keep
    track myself of the position in the string and not modify the string at
    all. This way

    - you can use the string returned by getenv() directly instead of having >> to copy it.

    - you don't have to call strlen() when you have already traversed the
    string to find the separator. Traversing the string for a second time
    probably won't have a noticeable effect for performance in this context
    but this kind of thing where the code has done a certain somewhat costly >> computation (traversing part of the string) and then immediately does
    again almost the same computation instead of using the result found by
    the first computation , grates me.

    I dunno, is it perfect? Nah. Is it a good start? Well, yeah =)

    I could not tell what it was supposed to do so I didn't comment, but
    there's definitely a "let's just try" sense to the code. For example,
    you examine and use three characters, but you don't test if three
    characters were actually read.

    One specific C issue did jump out at me:

    "A binary stream need not meaningfully support fseek calls with a
    whence value of SEEK_END."

    --
    Ben.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Ben Bacarisse on Sun Feb 25 01:51:58 2024
    Ben Bacarisse <[email protected]> wrote:

    ? I don't know, but I think you might have thought twice about that -1.

    Aye, good point.

    I could not tell what it was supposed to do so I didn't comment, but
    there's definitely a "let's just try" sense to the code. For example,
    you examine and use three characters, but you don't test if three
    characters were actually read.

    Well, that's all that it is though Ben, the development of an idea.
    (Thinking aloud was term I used), maybe I'll whittle on it more
    sometime, maybe you can tweak it too ya'know, post some code,
    live life, be happy Mr. Crabby =)

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Keith Thompson on Sun Feb 25 11:49:25 2024
    Keith Thompson <[email protected]> wrote:

    It's recommended *not* to include the current directory in $PATH.

    I've been thinking about that very thing in fact. It renders my
    idea of a binary reading a few bytes from its-self all but dead.
    I wont subvert a given security model for a configuration flag
    or two...

    But there you have it, a wonky idea, I've got plenty of them.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Spiros Bousbouras on Sun Feb 25 18:42:04 2024
    Spiros Bousbouras <[email protected]> writes:
    On Tue, 20 Feb 2024 06:56:06 -0000 (UTC)
    [email protected] (Mike Sanders) wrote:

    Just thinking aloud... Imagining here allowing more advanced users of
    my app to modify a string of digits residing at the end of my project's
    binary/exe. When the app loads, it reads in those digits. So far (knock
    on wood), I've not run up against a single case where the code below has
    failed to work under multiple OSs. A method mostly for power user's.d

    Talk about reinventing the wheel.

    That was a feature in 1960's operating systems. Usually called
    a switch register. Which was implemented first in hardware,
    and later in the operating system. In several systems, the
    OS (or binary loader) would load the value into some part
    of the application's memory before it starts (in one case,
    at address zero).

    Using an environment variable is far preferable, at least on
    unix-like or POSIX operating environments.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Keith Thompson on Sun Feb 25 22:58:37 2024
    On Sun, 25 Feb 2024 14:54:35 -0800, Keith Thompson wrote:

    [email protected] (Mike Sanders) writes:
    Keith Thompson <[email protected]> wrote:
    It's recommended *not* to include the current directory in $PATH.

    I've been thinking about that very thing in fact. It renders my
    idea of a binary reading a few bytes from its-self all but dead.
    I wont subvert a given security model for a configuration flag
    or two...

    But there you have it, a wonky idea, I've got plenty of them.

    Under Linux, a program can find its executable via /proc/$$/exe .

    With the caveat that /proc/$$/exe is a symlink.
    As such, it may participate in a race condition where, if the
    "real" executable (the target of the symlink) is deleted or
    moved before the program references it's /proc/$$/exe, the
    symlink may point to a non-existent, or /different/ executable.


    --
    Lew Pitcher
    "In Skills We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Spiros Bousbouras on Mon Feb 26 15:15:11 2024
    Spiros Bousbouras <[email protected]> wrote:


    For example , your code has

    fseek(bin, -3, SEEK_END); // jump 3 bytes before end of file
    unsigned char bytes[3];
    fread(bytes, 1, 3, bin);

    .Just reading this it is not immediate clear whether all appearances of 3 refer to the same conceptual entity or different ones which all happen to have the value 3. Using a constant would have made it clear that it is the former. Additionally , if you ever want to change the value , it is simpler to only do it in one place.

    I want to contest this point since its scope is always/only within that function, but... yes you are correct in the final analysis.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Scott Lurndal on Mon Feb 26 15:20:48 2024
    Scott Lurndal <[email protected]> wrote:

    Talk about reinventing the wheel.

    Could'nt afford the dealership's rates...

    That was a feature in 1960's operating systems. Usually called
    a switch register. Which was implemented first in hardware,
    and later in the operating system. In several systems, the
    OS (or binary loader) would load the value into some part
    of the application's memory before it starts (in one case,
    at address zero).

    Using an environment variable is far preferable, at least on
    unix-like or POSIX operating environments.

    Interesting stuff.

    Ya know, speaking only for myself, its a good idea in some
    respects, I just havent found a real reason to use it. That
    & where Keith is saying stay out the of the current directory
    (& he's right about that at least in Unix-like OSs).

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Spiros Bousbouras on Mon Feb 26 21:22:33 2024
    Spiros Bousbouras <[email protected]> writes:
    fseek(bin, -3, SEEK_END); // jump 3 bytes before end of file

    A binary stream need not meaningfully support fseek calls
    with a whence value of SEEK_END.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Spiros Bousbouras on Mon Feb 26 21:54:20 2024
    Spiros Bousbouras <[email protected]> writes:
    On 26 Feb 2024 21:22:33 GMT
    [email protected] (Stefan Ram) wrote:
    Spiros Bousbouras <[email protected]> writes:
    fseek(bin, -3, SEEK_END); // jump 3 bytes before end of file
    A binary stream need not meaningfully support fseek calls
    with a whence value of SEEK_END.
    The way your are quoting me gives the impression that I wrote the code.
    I didn't , I was quoting Mike Sanders quote.

    Sorry! My newsreader has this standard attribution "writes".
    It should be: "the post at the end of the References header
    line contains:". Maybe I can change it. Your post contained
    "fseek(bin, -3, SEEK_END);", but you have not written this.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to All on Tue Feb 27 15:55:58 2024
    Lines beginning with ">" are from the post at the end of the References: >fseek(bin, -3, SEEK_END);

    I am now testing a new attribution line added by my post script
    (which I have written in Python). - If one still should read
    "writes:", above something went wrong.

    While I usually do post test posts to test newsgroups, in this case,
    I post to "comp.lang.c", so that Spiros can see my new attribution
    line and complain again should he still deem it to be misleading.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Stefan Ram on Tue Feb 27 16:17:16 2024
    [email protected] (Stefan Ram) writes:
    Lines beginning with ">" are from the post at the end of the References: >>fseek(bin, -3, SEEK_END);

    I am now testing a new attribution line added by my post script
    (which I have written in Python). - If one still should read
    "writes:", above something went wrong.

    While I usually do post test posts to test newsgroups, in this case,
    I post to "comp.lang.c", so that Spiros can see my new attribution
    line and complain again should he still deem it to be misleading.

    If not misleading, it is still useless. The standard attribution
    lines are far more readable and useful. Nobody wants to go searching
    for a specific message id in order to determine who you are responding
    to. And in replies, such as this one, the message-Id in question is
    no longer at the end of the References: header, so your attribution line becomes invalid.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Stefan Ram on Tue Feb 27 18:20:01 2024
    On 27/02/2024 16:55, Stefan Ram wrote:
    Lines beginning with ">" are from the post at the end of the References:
    fseek(bin, -3, SEEK_END);

    I am now testing a new attribution line added by my post script
    (which I have written in Python). - If one still should read
    "writes:", above something went wrong.

    While I usually do post test posts to test newsgroups, in this case,
    I post to "comp.lang.c", so that Spiros can see my new attribution
    line and complain again should he still deem it to be misleading.

    Your normal attribution was fine. Just don't snip so much context that
    the post is meaningless. (It's not a big deal - sometimes people snip inaccurately.)

    But you /do/ have a problem with your indentation. It would be easier
    to keep track of attributions and things in your posts if you indented
    like everyone else, instead of doing it backwards.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Spiros Bousbouras on Tue Feb 27 18:54:35 2024
    Spiros Bousbouras <[email protected]> wrote or quoted:
    You're overcomplicating things.

    My reference to the header "References" was too technical,
    and it might not be good not to mention the author of the
    referred post. So now I have instead changed the "writes:"
    provided by my newsreader into: "wrote or quoted".

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From immibis@21:1/5 to Mike Sanders on Wed Feb 28 22:21:08 2024
    On 20/02/24 07:56, Mike Sanders wrote:
    Just thinking aloud... Imagining here allowing more advanced users of
    my app to modify a string of digits residing at the end of my project's binary/exe. When the app loads, it reads in those digits. So far (knock
    on wood), I've not run up against a single case where the code below has failed to work under multiple OSs. A method mostly for power user's.

    The user simply opens the compiled project in their favorite hex editor
    & modifies the pre-existing string, a pseudo bitmask of sorts. Certainly
    the user could unwittingly add/remove bytes rather than only modifying the bytes, still its a nifty idea. But practical? I'm not so sure on that point.

    Any other gotchas of note?

    If they can only modify bytes, not add or remove, you can just put some
    bytes anywhere in your code and the user can find them modify them with
    a hex editor. You don't need to do anything special to allow someone to
    modify a file in their possession.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to immibis on Fri Mar 1 00:38:33 2024
    immibis <[email protected]> wrote:

    If they can only modify bytes, not add or remove, you can just put some
    bytes anywhere in your code and the user can find them modify them with
    a hex editor. You don't need to do anything special to allow someone to modify a file in their possession.

    Thanks for your input, I appreciate it.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From [email protected]@21:1/5 to [email protected] on Fri Mar 1 18:37:17 2024
    On Sun, 25 Feb 2024 14:54:35 -0800, Keith Thompson <[email protected]> wrote:

    [email protected] (Mike Sanders) writes:
    Keith Thompson <[email protected]> wrote:
    It's recommended *not* to include the current directory in $PATH.

    I've been thinking about that very thing in fact. It renders my
    idea of a binary reading a few bytes from its-self all but dead.
    I wont subvert a given security model for a configuration flag
    or two...

    But there you have it, a wonky idea, I've got plenty of them.

    Under Linux, a program can find its executable via /proc/$$/exe .

    YM sprintf(buf, "/proc/%d/exe", getpid()) // or if paranoid snprintf
    because substituting for $$ is a feature of shell and perl only. But /proc/self/exe works in anything.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to [email protected] on Sat Mar 2 01:10:09 2024
    [email protected] wrote:

    YM sprintf(buf, "/proc/%d/exe", getpid()) // or if paranoid snprintf
    because substituting for $$ is a feature of shell and perl only. But /proc/self/exe works in anything.

    For Linux, sure. But the BSDs & Apple & Win...

    Reading itsself, its real self, seems to me to be the only
    cross platform way.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Mike Sanders on Sat Mar 2 02:36:14 2024
    On Sat, 02 Mar 2024 01:10:09 +0000, Mike Sanders wrote:

    [email protected] wrote:

    YM sprintf(buf, "/proc/%d/exe", getpid()) // or if paranoid snprintf
    because substituting for $$ is a feature of shell and perl only. But
    /proc/self/exe works in anything.

    For Linux, sure. But the BSDs & Apple & Win...

    Reading itsself, its real self, seems to me to be the only
    cross platform way.

    Perhaps, but there seems to be no cross-platform way for a program
    to determine the "itself" to read.


    --
    Lew Pitcher
    "In Skills We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Lew Pitcher on Sat Mar 2 12:13:27 2024
    Lew Pitcher <[email protected]> wrote:

    For Linux, sure. But the BSDs & Apple & Win...

    Reading itsself, its real self, seems to me to be the only
    cross platform way.

    Perhaps, but there seems to be no cross-platform way for a program
    to determine the "itself" to read.

    Another thing too Lew... what if there is *no* path (for instance
    something like a small controller/embedded environment)? It couldn't
    find itsself in any event then I'm thinking.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mike Sanders@21:1/5 to Spiros Bousbouras on Sat Mar 2 12:16:12 2024
    Spiros Bousbouras <[email protected]> wrote:

    Which may be the wrong order. Regardless of the overall merits of your idea , it should mimic as close as possible how the system finds executables. This means not search in the current directory unless the appropriate constructs exist in PATH and search the components in the same order as they appear in PATH .This implies recognising :: in PATH and my point is that using strtok() (apart from its other issues) makes it harder to do this.

    Yes, sounds like good advice to me Spiros.

    --
    :wq
    Mike Sanders

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)