1

I'm working on a project which has to mount samba shares on Ubuntu. This project will be used by non-root users. At now I'm using an application called gvfs-mount because that doesn't require the root password for mounting.

My application runs that executable with specific command line arguments and it works, but the error checking is difficult. I'm using a library called pstreams to launch the gvfs-mount and to write and read to it's stdin/out, but I can predict when the application will write something to stdout. And that is a problem, because if I want to read something from the gvfs-mount's output, but the application doesn't wrote anything, the host application will be blocked, because that will wait for something that will never come.

I know that I could use the mount function from sys/mount.h, but that would require root privileges. My question is: Is there any API, library or tutorial about this topic in C++?

Edit:

As filmor mentioned I had a look to gvfs-mount's source code and I converted to C++. Here is my very basic code:

#include <gtkmm.h>

#include <stdexcept>
#include <iostream>

Glib::RefPtr<Gio::File> file;
Glib::RefPtr<Glib::MainLoop> main_loop;

void on_async_ready(Glib::RefPtr<Gio::AsyncResult>& result)
{
file->mount_enclosing_volume_finish(result);

main_loop->quit();
}

int main()
{
Gio::init();
Glib::init();

main_loop = Glib::MainLoop::create(false);

file = Gio::File::create_for_commandline_arg("smb://192.168.1.3/Memory\\ core");
Glib::RefPtr<Gio::MountOperation> mount_operation = Gio::MountOperation::create();
mount_operation->set_domain("domain");
mount_operation->set_username("user");
mount_operation->set_password("password");

try
{
file->mount_enclosing_volume(mount_operation, &on_async_ready);
}
catch(const Glib::Error& ex)
{
std::cerr << ex.what() << std::endl;
}

main_loop->run();

return 0;
}

The problem is that when I run this code as normal user I get this output:

(process:5816): glibmm-CRITICAL **: unhandled exception (type Glib::Error) in signal handler: domain: g-io-error-quark code : 0 what : Failed to mount Windows share: Operation not permitted

When I run as sudo I get this:

(process:5862): glibmm-CRITICAL **: unhandled exception (type Glib::Error) in signal handler: domain: g-io-error-quark code : 15 what : volume doesn't implement mount

Any suggestion about solving this? The code should work with normal user privileges.

Edit 2:

I updated the source code, because it was an error in uri. I found that if I run the gvfs-mount as sudo, I get the same error message like in my application. So my idea is that there is something wrong with permissions. My username belongs to fuse group it that matters.

#include <gtkmm.h>

#include <iostream>

Glib::RefPtr<Gio::File> file;
Glib::RefPtr<Glib::MainLoop> main_loop;

void on_async_ready(Glib::RefPtr<Gio::AsyncResult>& result)
{
    try
    {
        file->mount_enclosing_volume_finish(result);
    }
    catch(const Glib::Error& ex)
    {
        std::cerr << ex.what() << std::endl;
    }

    main_loop->quit();
}

int main()
{
    Gio::init();
    Glib::init();

    main_loop = Glib::MainLoop::create(false);

    file = Gio::File::create_for_commandline_arg("smb://192.168.1.3/Memory core");
    Glib::RefPtr<Gio::MountOperation> mount_operation = Gio::MountOperation::create();
    mount_operation->set_domain("domain");
    mount_operation->set_username("user");
    mount_operation->set_password("password");


    file->mount_enclosing_volume(mount_operation, &on_async_ready);

    main_loop->run();

    return 0;
}
user438383
  • 5,716
  • 8
  • 28
  • 43
Szőke Szabolcs
  • 511
  • 7
  • 19
  • possible duplicate of [How to implement a timeout in read function call?](http://stackoverflow.com/questions/2917881/how-to-implement-a-timeout-in-read-function-call) – Anton Savin Sep 09 '14 at 08:18
  • 1
    `gvfs-mount` is not an excessive amount of code: https://git.gnome.org/browse/gvfs/tree/programs/gvfs-mount.c – filmor Sep 09 '14 at 08:44
  • Can you mount that share from the command line? If your system is not set up for mounting by unprivileged users, no amount of coding on your part will save the day. – n. m. could be an AI Sep 09 '14 at 17:01
  • Yes, I the same uri works well with gvfs-mount. – Szőke Szabolcs Sep 09 '14 at 17:30
  • It is my impression that `gvfs-mount` just uses `mount(2)` under the hood. You can compile a debug version and test it under debugger... – n. m. could be an AI Sep 11 '14 at 18:29
  • @SzőkeSzabolcs did you ever happen to solve this? I'm just getting started with GIO and GVFS and am running the same "Operation not permitted" error you reported. Using `gio mount` from CLI works just fine. – Marcus Ilgner Mar 21 '22 at 13:20
  • In order to find out what's going on, I traced the network traffic using Wireshark. There I can see that the server is returning `STATUS_MORE_PROCESSING_REQUIRED` in response to the `NTLMSSP_NEGOTIATE` request. This also seems to happen twice. When using CLI, it looks similar but the second request is followed by a `NTLMSSP_AUTH` request which seems to be missing when using the mount operation with credentials. – Marcus Ilgner Mar 21 '22 at 14:32

1 Answers1

0

I was able to resolve this problem in my Rust application which at first showed the same behaviour as reported in this question.

The solution was to register a callback for the ask-password signal, use this code path to fill in the credentials and then - most importantly - call reply on the mount operation with the Handled flag.

PoC in Rust attached, should transfer easily to C++, too:


use gio::prelude::*;
use glib::{self, clone};
use futures::prelude::*;
use gio::{AskPasswordFlags, MountMountFlags, MountOperation, MountOperationResult};

// read_file taken from https://github.com/gtk-rs/gtk-rs-core/blob/master/examples/gio_futures_await/main.rs#L29

async fn read_file(file: gio::File) -> Result<(), String> {
    // Try to open the file.
    let strm = file
        .read_future(glib::PRIORITY_DEFAULT)
        .map_err(|err| format!("Failed to open file: {}", err))
        .await?;

    // If opening the file succeeds, we asynchronously loop and
    // read the file in up to 64 byte chunks and re-use the same
    // vec for each read.
    let mut buf = vec![0; 64];
    let mut idx = 0;

    loop {
        let (b, len) = strm
            .read_future(buf, glib::PRIORITY_DEFAULT)
            .map_err(|(_buf, err)| format!("Failed to read from stream: {}", err))
            .await?;

        // Once 0 is returned, we know that we're done with reading, otherwise
        // loop again and read another chunk.
        if len == 0 {
            break;
        }

        buf = b;

        println!("line {}: {:?}", idx, std::str::from_utf8(&buf[0..len]).unwrap());

        idx += 1;
    }

    // Asynchronously close the stream in the end.
    let _ = strm
        .close_future(glib::PRIORITY_DEFAULT)
        .map_err(|err| format!("Failed to close stream: {}", err))
        .await?;

    Ok(())
}

// one could probably also use glib to drive the futures
// but this was more familiar to me

#[tokio::main]
async fn main() {
    env_logger::init();

    let c = glib::MainContext::default();

    let file = gio::File::for_uri("smb://host/users/username/Desktop/test.txt");

    // check whether the surrounding share is already mounted
    let cancellable = gio::Cancellable::new();
    if file.find_enclosing_mount(Some(&cancellable)).is_err() {
        log::info!("Enclosing share not mounted, trying to mount it");

        let mount_op = MountOperation::new();
        mount_op.connect_ask_password(|op, msg, default_user, default_domain, flags| {
            op.set_anonymous(false);
            if flags.contains(AskPasswordFlags::NEED_USERNAME) {
                op.set_username(Some("my-user"));
            }
            if flags.contains(AskPasswordFlags::NEED_PASSWORD) {
                op.set_password(Some("my-password"));
            }
            if flags.contains(AskPasswordFlags::NEED_DOMAIN) {
                op.set_domain(Some(default_domain)); // should not be required, let's see
            }
            // this is the important part!
            op.reply(MountOperationResult::Handled);
        });

        let mount_result = file.mount_enclosing_volume_future(MountMountFlags::empty(), Some(&mount_op));
        let mount_result = c.block_on(mount_result);
        if let Err(err) = mount_result {
            log::error!("Failed to mount: {}", err);
            return
        }
    }

    let future = async {
        match read_file(file).await {
            Ok(()) => (),
            Err(err) => eprintln!("Got error: {}", err),
        }
    };

    c.block_on(future);
}
Marcus Ilgner
  • 6,935
  • 2
  • 30
  • 44