InsaneCoder
InsaneCoder

Reputation: 8268

Linux : setting locale at runtime and interprocess dependencies

I am stuck in a strange problem.

I have two scripts (C program executables) running on ARM linux machine that are mounting the same USB device (containing chinese character filenames) on two different paths, as soon as the device is inserted.

int mount(const char *source, const char *target,
                 const char *filesystemtype, unsigned long mountflags,
                 const void *data);

In the last parameter, Script A passes "utf8" and Script B passes 0.

So, as soon as I insert the USB device, the scripts race to mount the device.

If Script A mounts first (which passes utf8 parameter), I get proper filenames. This is the mount command output [Notice that even second mount has utf8 as parameter, even if its not passed. Why?]

/dev/sdb1 on /home/root/script1 type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-r
o)
/dev/sdb1 on /home/root/script2 type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed
,utf8,errors=remount-ro)

But if script B mounts first(which passes 0 as last parameter to mount), I get broken filenames ?????.mp3 from readdir(). This is the mount command output.

/dev/sdb1 on /home/root/script2 type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
/dev/sdb1 on /home/root/script1 type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed
,errors=remount-ro)         

EDIT

This is the basic mount code of both the scripts developed for testing(only difference in last mount argument). Both scripts are executed immediately on reboot using a service.

//mount the device
        ret = mount("/dev/sda1", "/home/root/script1/", "vfat", 1, "utf8");
        if (ret == 0) {
                fprintf(stdout,"mount() succeeded.\n");
                sleep(2000);
        } else {
                ret = mount("/dev/sdb1", "/home/root/script1/", "vfat", 1, "utf8");
                if(ret == 0)
                {
                    fprintf(stdout,"mount() succeeded\n");
                    sleep(2000);
                }
                else
                {
                    fprintf(stdout,"/dev/sdb1 mount() failed: %d, %s\n", errno, strerror(errno));
                    ret = mount("/dev/sdc1", "/home/root/script1/", "vfat", 1, "utf8");
                    if(ret == 0)
                    {
                        fprintf(stdout,"mount() succeeded\n");
                        sleep(2000);
                    }
                    else
                        fprintf(stdout,"mount() failed: %d, %s\n", errno, strerror(errno));
                }
        }

Upvotes: 1

Views: 100

Answers (1)

myaut
myaut

Reputation: 11504

Generally speaking, you should never mount same filesystem twice -- if OS drivers will decide to write twice to the same block, you'll get filesystem corruption. Use bind-mounts in such cases.

Linux, however, is smart enough to help you with that -- it will reuse older filesystem mount super_block (with all mountpoint flags) for a to a location.

I couldn't find it in documentation, but it is traceable through kernel source in sget() which is called by mount_bdev():

hlist_for_each_entry(old, &type->fs_supers, s_instances) {
        if (!test(old, data))
                continue;
        if (!grab_super(old))
                goto retry;
        if (s) {
                up_write(&s->s_umount);
                destroy_super(s);
                s = NULL;
        }
        return old;
}

In this snippet it'll seek for previous instance of super_block corresponding to a block device, and if it already exists -- simply returns it.


Some practical proof using SystemTap:

# stap -e 'probe kernel.function("sget").return {
        sb = $return;
        active = @cast(sb, "super_block")->s_active->counter;
        fsi = @cast(sb, "super_block")->s_fs_info;
        uid = fsi == 0 ? -1
            : @cast(fsi, "msdos_sb_info", "vfat")->options->fs_uid;
        printf("%p active=%d uid=%d\n", sb, active, uid);
    }'

Setting uid in second mount doesn't alter option, but increases number of active mounts (obvious):

  # mount /dev/sdd1 /tmp/mnt1
  0xffff8803ce87e800 active=1 uid=-1
  # mount -o uid=1000 /dev/sdd1 /tmp/mnt2
  0xffff8803ce87e800 active=2 uid=0

Mounting in reverse order also inherits mount options:

  # mount -o uid=1000 /dev/sdd1 /tmp/mnt2
  0xffff8803cc609c00 active=1 uid=-1
  # mount /dev/sdd1 /tmp/mnt1
  0xffff8803cc609c00 active=2 uid=1000

If you wish to know who was responsible for such behavior, ask Linus, similiar code exists since 0.11:

struct super_block * get_super(int dev)
{
    struct super_block * s;

    if (!dev)
        return NULL;
    s = 0+super_block;
    while (s < NR_SUPER+super_block)
        if (s->s_dev == dev) {
            wait_on_super(s);
            if (s->s_dev == dev)
                return s;
            s = 0+super_block;
        } else
            s++;
    return NULL;
}

(but when this code was in charge, sys_mount() explicitly checked that no other mountpoints exist for that superblock).

You can possibly try to ask a question at LKML.

Upvotes: 3

Related Questions