Author Topic: Questions about porting 'file'  (Read 475 times)

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Questions about porting 'file'
« on: August 05, 2026, 12:36:12 am »
I'm in a little over my head, but that is normal. Life is for learning.

Nobody has provided an up-to-date version of the 'file' utility, so I decided to do it myself.  What we have is almost ten years old. Due to bitrot, newer versions have not even been buildable on OS/2. I opened a few tickets on the file bugtracker and version 5.48 now not only compiles but it fixes a couple of longstanding OS/2-related bugs.

Most of what I know is how to type "autoreconf", "configure", and "make".  Maybe someone can answer a few beginner's questions--

Q1:
in tests/Makefile.am is a piece of shell script that includes the line
        m=$$m${PATH_SEPARATOR}$$j; \
which confuses sh.exe because our path separator is a semicolon.
What seems to fix it is quoting the line:
        m="$$m${PATH_SEPARATOR}$$j"; \
Is that the correct way to fix it? This must be a common situation. Before I suggest a fix to the maintainer I'd like to know it is the right fix.

Q2:
compiling the file_os2_apptype function causes two warnings:

    ../../src/apptype.c: In function 'file_os2_apptype':
    ../../src/apptype.c:61:27: warning: '%s' directive writing up to 255 bytes into a region of size between 3 and 260 [-Wformat-overflow=]
       61 |  (void)sprintf(path, "%s%s%s%s", drive,
          |                           ^~
       62 |   (*dir == '\0') ? "./" : dir,
       63 |   fname,
          |   ~~~~~
    ../../src/apptype.c:61:8: note: 'sprintf' output between 1 and 768 bytes into a destination of size 260
       61 |  (void)sprintf(path, "%s%s%s%s", drive,
          |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       62 |   (*dir == '\0') ? "./" : dir,
          |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       63 |   fname,
          |   ~~~~~~
       64 |   (*ext == '\0') ? "." : ext);
          |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~

I think I can see the reason-- the _MAX_PATH is less than the total of _MAX_DRIVE, _MAX_DIR, _MAX_FNAME, etc. Can I safely ignore those warnings? I suspect they have been around for years. The relevant code is

    #include <stdlib.h>
    #include <string.h>

    #ifdef __EMX__
    #include <io.h>
    #define INCL_DOSSESMGR
    #define INCL_DOSERRORS
    #define INCL_DOSFILEMGR
    #include <os2.h>
    typedef ULONG   APPTYPE;

    file_protected int
    file_os2_apptype(struct magic_set *ms, const char *fn, const struct buffer *b)
    {
        APPTYPE         rc, type;
        char            path[_MAX_PATH], drive[_MAX_DRIVE], dir[_MAX_DIR],
                fname[_MAX_FNAME], ext[_MAX_EXT];
        char           *filename;
        FILE           *fp;

        if (fn)
            filename = strdup(fn);
        else if ((filename = tempnam("./", "tmp")) == NULL) {
            file_error(ms, errno, "cannot create tempnam");
            return -1;
        }
        /* qualify the filename to prevent extraneous searches */
        _splitpath(filename, drive, dir, fname, ext);
        (void)sprintf(path, "%s%s%s%s", drive,
            (*dir == '\0') ? "./" : dir,
            fname,
            (*ext == '\0') ? "." : ext);

Q3:
when I do a "make install" it gives a red-letter warning:
        warning: remember to run 'libtool --finish /usr/local/lib'
What does that do and when should it be done? Before I package it or after the user installs it? It doesn't seem to change anything that I can see, so I'm ignoring the warning for now.

Considering what I don't know, the whole process has been surprisingly easy. It makes me suspicious.

The attached file is what I plan to upload to Hobbes, unless someone can find anything wrong with it.
All suggestions are welcome.


Dave Yeo

  • Hero Member
  • *****
  • Posts: 6052
  • Karma: +167/-1
Re: Questions about porting 'file'
« Reply #1 on: August 05, 2026, 03:16:32 am »
Hi.
What configure arguments are you using?
Q1, what is the error?
Here, make check ends quickly with,
Code: [Select]
Running test: ../tests/Animated_PNG_example_bouncing_beach_ball
TZ=UTC MAGIC=../magic/magic ./test -e ../tests/Animated_PNG_example_bouncing_beach_ball.testfile ../tests/Animated_PNG_example_bouncing_beach_ball.result
test: ERROR: result was (len 99)
 Animated PNG image data, 100 x 100, 8-bit/color RGBA, non-interlaced, 20 frames, play indefinitely
expected (len 99)
Animated PNG image data, 100 x 100, 8-bit/color RGBA, non-interlaced, 20 frames,
 play indefinitely
../tests/Animated_PNG_example_bouncing_beach_ball.testfile:  Animated PNG image
data, 100 x 100, 8-bit/color RGBA, non-interlaced, 20 frames, play indefinitely
make[2]: *** [Makefile:819: check-local] Error 1
With and without your patch. I suspect a EOL issue.
Q2:
Sometimes these warnings matter, often they can be ignored. Watch out for crashes related to this code. Good to include an xqs or dbg file for exceptq.
Q3: It's a *nix thing where libraries are cached and the cache has to be updated. Can't think of the exact command, maybe ldconfig.
Here of course, just have to get the LIBPATH correct. I had to use BEGINLIBPATH to avoid bus error (sys1808)

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: Questions about porting 'file'
« Reply #2 on: August 05, 2026, 05:18:29 am »
Dave,

configure arguments are --prefix=/usr/local --enable-fsect-man5  --disable-silent-rules

with an unpatched Makefile, make check should end with this:

Running test: ../../tests/multiple
../../tests/multiple-B.magic: 1: ../../tests/multiple-B.magic: 6: not found
../../tests/multiple-B.magic: 2: ../../tests/multiple-B.magic: 10: not found
make.exe[2]: *** [check-local] Error 127
make.exe[2]: Leaving directory `F:/src/file-nopatch/build/tests'
make.exe[1]: *** [check-am] Error 2
make.exe[1]: Leaving directory `F:/src/file-nopatch/build/tests'
make.exe: *** [check-recursive] Error 1

That should be the only error message you get from make check, but it is hardly informative.  The problem is that on OS/2 file.exe, or rather test.exe, needs to be called with "-m multiple-A.magic;multiple-B.magic". Note the semicolon as a path separator between the names of the magic files. So when sh.exe builds the list of magic files
        m=$$m${PATH_SEPARATOR}$$j; \
translates as
        m=multiple-A.magic;multiple-B.magic; \
I =think= sh.exe misinterprets the first semicolon as the end of the clause or whatever. So quoting that line in the Makefile protects the path_separator semicolon from being interpreted. It works, but I don't know enough about shell scripting to know if that is the accepted way of doing it.

But wait a minute-- file 5.48 does not even have a test file called Animated_PNG_example_bouncing_beach_ball.testfile. It does exist in the current version from github, and I get the same error you do. Sounds like a new bug.

Yes, BEGINLIBPATH has to be set in a couple of places. That's partly why I wrote build_file.btm; it's easier than doing each step manually.

Dave Yeo

  • Hero Member
  • *****
  • Posts: 6052
  • Karma: +167/-1
Re: Questions about porting 'file'
« Reply #3 on: August 05, 2026, 07:26:55 am »
OK, I checked out FILE5.48 and can reproduce your results.
I'm far from an expert but I'm inclined to fix it like,
Code: [Select]
m=$$m'${PATH_SEPARATOR}'$$j; \
Not sure which is more correct.

For sending patches etc upstream, I usually check out head so fixes can be in version next.
The Animated_PNG_example_bouncing_beach_ball.testfile was added Apr 17 along with apng_testfile.ffmpeg.testfile and the corresponding results files.
The weird thing is they haven't been added to the build system so why they're being run?
Testing by changing the MAKESHELL to bash, they don't get tested, rather it dies early here,
Code: [Select]
Running test: ../tests/android-vdex-1
TZ=UTC MAGIC=../magic/magic ./test -e ../tests/android-vdex-1.testfile ../tests/android-vdex-1.result
test: ERROR: result was (len 124)
 Android vdex file, verifier deps version: 021, dex section version: 002, number of dex files: 4, verifier deps size: 106328
expected (len 124)
Android vdex file, verifier deps version: 021, dex section version: 002, number of dex files: 4, verifier deps size: 106328
../tests/android-vdex-1.testfile:  Android vdex file, verifier deps version: 021
, dex section version: 002, number of dex files: 4, verifier deps size: 106328
make[2]: *** [Makefile:819: check-local] Error 1
very similar error and looking, it seems that it actually passed but is getting evaluated wrong, same as the other failure. Have to see what changed.

About configure options. I would use --prefix=/@unixroot/usr/local
The usual other options are, CFLAGS=-march=i686 'LDFLAGS=-Zomf -Zhigh-mem -Zmap'
Target the i686, link with the system linker which usually works better and currently needed to debug. Use high memory if possible. Create a map file for diagnostics or to create a xqs file, "mapxqs file.map"
Usually also LIBS=-lcx, which in this case will bring in mmap. Tried and make check failed with something like mprotect failed due to file.exe (or was it test.exe) being locked.
Your other options are fine.
« Last Edit: August 05, 2026, 07:28:44 am by Dave Yeo »

Jochen Schäfer

  • Sr. Member
  • ****
  • Posts: 422
  • Karma: +37/-0
Re: Questions about porting 'file'
« Reply #4 on: August 05, 2026, 09:16:52 am »
Q2: snprintf is the safe variant of sprintf. Obviously, setting a max string size is not always correct, but it is safe.

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: Questions about porting 'file'
« Reply #5 on: August 05, 2026, 09:26:15 pm »
Thanks Dave and Jochen

I knew it seemed too easy...

using "configure --prefix=/@unixroot/usr/local" is a good idea, because file uses the prefix to find its default magic file.  /usr/local would mean that file would not work outside the %unixroot drive.

But the prefix is also used inside the man files. doc/Makefile calls sed using @ as a separator character, like
    sed -e s@__MAGIC__@${MAGIC}@g $(srcdir)/file.man > $@
where ${MAGIC} is /@unixroot/usr/local. The extra @ confuses sed.  I can deal with sed by double-escaping the prefix--
    --prefix=/\\@unixroot/usr/local
but then file complains that it cannot find its magic file /\@unixroot/usr/local/share/misc/magic

I could just tell the users that they =must= set MAGIC=/@unixroot/usr/local/share/misc/magic in their environment, but that is ugly.
Or I could patch doc/Makefile.am to use some other character for a separator, like '?'. I tried it and it works.

Any other possibilities? Bww apparently solved the problem in their file 5.30 package.


Dave Yeo

  • Hero Member
  • *****
  • Posts: 6052
  • Karma: +167/-1
Re: Questions about porting 'file'
« Reply #6 on: August 05, 2026, 11:59:11 pm »
Have you looked at the src RPM?
Code: [Select]
wget http://rpm.netlabs.org/release/00/pentium4/SRPMS/file-5.30-2.oc00.src.rpm
unrpm -e file-5.30-2.oc00.src.rpm
Don't see a prefix in the spec, probably taken care of by a macro. There is the Python stuff to install and a forwarder DLL. Also no patches.
Guess could try building the RPM and log the build.

KO Myung-Hun

  • Full Member
  • ***
  • Posts: 117
  • Karma: +26/-0
Re: Questions about porting 'file'
« Reply #7 on: August 06, 2026, 07:03:56 am »
Hi/2.

For $PATH_SEPARATOR, it seems to be a shell variable because it's called ${PATH_SEPARATOR}. So quoting with "" is right. You can quote entire right side of '=', or only ${PATH_SEPARATOR}. Quoting with '' casues sh to interpret ${PATH_SEPARATOR} itself literally without expanding it.

For /@unixroot, why not change sed separator '@' to other character such as ',' ?

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: Questions about porting 'file'
« Reply #8 on: August 07, 2026, 02:45:27 am »
Dave, thanks for the link to the rpm source. I knew it must be somewhere.

KO Myung-Hun suggested changing the sed separator and that is also what Bww did in file 5.30, so I'm on the right track. Done that.


I also did a diff to see what else Bww changed.  I am =definitely= in over my head now, but it is interesting. Several of the changes are along the lines of

   +#ifdef __OS2__
   +   FILE *f = fopen(ms->file = fn, "rb");
   +#else
       FILE *f = fopen(ms->file = fn, "r");
   +#endif

   +#ifdef __OS2__
   +      tfd = open(ptr, O_RDWR|O_TRUNC|O_EXCL|O_CREAT|O_BINARY, 0600);
   +#else
           tfd = open(ptr, O_RDWR|O_TRUNC|O_EXCL|O_CREAT, 0600);
   +#endif

   +#ifdef __OS2__
   +   /* Place stdin in binary mode, so EOF (Ctrl+Z) doesn't stop early. */
   +   if (fd == STDIN_FILENO)
   +      setmode(STDIN_FILENO, O_BINARY);
   +#endif

That last one causes a warning about an implicit declaration of function 'setmode', so I added to the top of magic.c
   #ifdef __OS2__
   #include <io.h>
   #endif

I don't really understand the reason for 'binary', but those changes are easy to insert into version 5.48.

But there are two that are completely meaningless to me. Should I worry about these? And what is __KLIBC__x? Maybe I should stick with what I've got and let someone else make a better version if they get motivated...

   diff -aur file-5.30-src/src/compress.c file-5.30-rpm/src/compress.c
   --- file-5.30-src/src/compress.c 2017-01-18 10:33:56.000000000 -0600
   +++ file-5.30-rpm/src/compress.c 2017-03-17 15:58:38.000000000 -0500
   @@ -79,6 +79,11 @@
    #define DPRINTF(...)
    #endif

   +#ifdef __EMX__
   +#include <sys/socket.h>
   +#define pipe(A) socketpair(AF_UNIX, SOCK_STREAM,0, A)
   +#endif
   +
    #ifdef ZLIBSUPPORT
    /*
     * The following python code is not really used because ZLIBSUPPORT is only
   @@ -200,6 +205,12 @@
       if ((ms->flags & MAGIC_COMPRESS) == 0)
           return 0;

   +#ifdef __KLIBC__x
   +// @todo: is this still needed? if yes enable it back, else delete it
   +// YD this code forks, and invoked from a dll make fork to crash
   +return 0;
   +#endif
   +
    #ifdef HAVE_SIGNAL_H
       osigpipe = signal(SIGPIPE, SIG_IGN);
    #endif


readelf.c has changed a lot. This one may no longer be needed:

   diff -aur file-5.30-src/src/readelf.c file-5.30-rpm/src/readelf.c
   --- file-5.30-src/src/readelf.c  2017-02-01 06:36:58.000000000 -0600
   +++ file-5.30-rpm/src/readelf.c  2017-03-04 20:56:20.000000000 -0600
   @@ -1185,7 +1185,7 @@
    {
       Elf32_Shdr sh32;
       Elf64_Shdr sh64;
   -   int stripped = 1, has_debug_info = 1;
   +   int stripped = 1, has_debug_info = 0;
       size_t nbadcap = 0;
       void *nbuf;
       off_t noff, coff, name_off;
   @@ -1373,12 +1373,12 @@
           }
       }

   -   if (file_printf(ms, ", %sstripped", stripped ? "" : "not ") == -1)
   -      return -1;
       if (has_debug_info) {
           if (file_printf(ms, ", with debug_info") == -1)
               return -1;
       }
   +   if (file_printf(ms, ", %sstripped", stripped ? "" : "not ") == -1)
   +      return -1;
       if (cap_hw1) {
           const cap_desc_t *cdp;
           switch (mach) {

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: Questions about porting 'file'
« Reply #9 on: August 07, 2026, 03:01:47 am »
I should have mentioned, if I apply all the 'open binary' patches, file objects if my local magic files have CRLF line endings. That won't do.

Dave Yeo

  • Hero Member
  • *****
  • Posts: 6052
  • Karma: +167/-1
Re: Questions about porting 'file'
« Reply #10 on: August 07, 2026, 04:08:25 am »
Opening files and pipes in binary mode is good practice when porting from *nix to DOSish systems such as OS/2. Often you can grep for Win32 (probably changed in the 64bit world) to see where binary mode is needed, along with including <io.h>.
Looking, I see in magic.c,
Code: [Select]
#ifdef WIN32
        /* Place stdin in binary mode, so EOF (Ctrl+Z) doesn't stop early. */
        if (fd == STDIN_FILENO)
                _setmode(STDIN_FILENO, O_BINARY);
#endif
So I guess another reason to use O_BINARY. Windows does prefix some of these functions with an underline.
Your local magic files shouldn't have CR/LF EOL, at least to be compatible with the Bitwise port and if someone updates magic.
I've had to patch for binary mode to pass test suites too.
Interesting that they patched to use a local socket instead of a pipe, might be a good idea to keep that though I don't know the reasoning unless it is to avoid pipe opening without binary mode.
I'd guess that __KLIBC__x means a future version of kLIBC or now libcn with the x for later version. The comment is informative.
The elf stuff, no idea except one change looks like only a white space change and can be ignored.

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: Questions about porting 'file'
« Reply #11 on: August 07, 2026, 05:49:20 am »
Telling OS/2 users not to use CRLF line endings seems burdensome. I wonder if something llke the following, in apprentice.c, would be reasonable. The problem seems to be only blank lines that end with CRLF. I don't know what file does with the other CRs. This works for me:

Code: [Select]
switch (line[0]) {
case '\0': /* empty, do not parse */
#ifdef __OS2__
case '\r': /* akm: empty CRLF line, do not parse */
case '\x1A': /* and the pesky Ctrl-Z, while we're at it */
#endif
case '#': /* comment, do not parse */
continue;

Dave Yeo

  • Hero Member
  • *****
  • Posts: 6052
  • Karma: +167/-1
Re: Questions about porting 'file'
« Reply #12 on: August 07, 2026, 07:37:35 am »
Well, generally OS/2 seems pretty agnostic when it comes to line endings. The exception being REXX which does need cr/lf.
Have you tested with a magic that uses LF as EOL? A git checkout sees using LF as EOL. An unzip might convert LF to CRLF.
Have you tried git master with your patch? Good to fix for the next version too, especially if you want patches accepted upstream.
I just tried master with adding -Zbin-files to LDFLAGS (-Zbin-files forces binary mode) and now, with your patch for tests/makefile.am, make check succeeds unlike the previous failures we noted.
It's your port but it is nice if your patches can be used for version next.

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: Questions about porting 'file'
« Reply #13 on: August 08, 2026, 10:17:59 pm »
Exactly. OS/2 is EOL-agnostic and an OS/2 version of file should be the same. That is what I have now.
Applying the 'open binary' patch for reading magic files breaks it. My idea for eliminating the CRs on blank lines won't work; I found out what happens to the other CRs.

This is interesting-- without the patch, when getline() reads a CRLF-terminated line, it seems to eat the CR. It returns an LF-terminated string-- <text>LF. Then file strips the LF. With the patch, getline() returns <text>CRLF, file strips the LF, leaving me with a stray CR at the end of the description. That would explain the behavior I see. File has its own version of getline, which I gather is not used, since config.h has
    #define HAVE_GETLINE 1

So for now I will leave out that patch until I learn more.

For my own education, I would like to find an explanation of why and when an 'open binary' patch is needed. A magic file is text-only, so is it needed in that case?

If I can get this working I will certainly submit some patches to the maintainer. Then porting the next version will be much easier.

Progress report:
I added a line to ignore the pesky Ctrl-Z EOF character, just in case someone uses one of those editors.
I left out the patch that starts with "#define pipe(A) socketpair(AF_UNIX, SOCK_STREAM,0, A)" because it turns out the function involved no longer uses pipe() anyway.
In one place I changed
     #ifdef WIN32
to
    #if defined (WIN32) || defined (__OS2__)
so it could check the environment for %temp, %tmp, %tmpdir




KO Myung-Hun

  • Full Member
  • ***
  • Posts: 117
  • Karma: +26/-0
Re: Questions about porting 'file'
« Reply #14 on: August 09, 2026, 08:45:16 am »
Hi/2.

For [b__KLIBC__x[/b], it is used to disable the code path temporarily on kLIBC. That is, it was the code path for kLIBC originally, so it was guarded with __KLIBC__.
To test or to use later for kLIBC, however, it was disabled by appending x like __KLIBC__x. After all, it is the kLIBC version of #if 0/#if 1.

For binary patch, file checks the binary files. To read them correctly, file should open them in binary mode. If opening in text mode, CRLF pairs in binary files will be converted to LF. As a result, the read data may be corrupted. To prevent this, binary patch is necessary. As a side effect, CR garbage at line end remains when reading text files. To fix this, you can use _crlf() if you can sure that the file is a text file. Or it is possible to change the mode of the file to text mode after opening it. As well as, you can remove the CR garbage at line end after reading a line. If the file which file program read is only the magic file, then convert CRLF pairs of them to LF at the beginning with utils such as dos2unix. And I recommend to read https://lvzuufx.blogspot.com/2014/09/porting-to-os2-case-1-line-ending.html.