Python copy files from Linux to WIndows

Question:

I’m building a website which has a form which captures user data and runs some cgi on the user data. One of the first steps of the cgi is that it needs to copy files from the linux webserver to windows machines. The server would be using an active directory role acount for the copy credential. I had hoped to simply use something like this:

mount -t cifs -o username=someUsername,password=somePasword //someMachine/someShare /someMountPoint

Unfortunately I get errors about the password attributed being invalid when I run that command in bash. Ideally I would use this method to mount the remote windows c$ share and then copy the files but I'm willing to try other modules if they make more sense.

I had something like this but it doesn't work, creates the necessary temporary directories but never mounts anything. I'm happy to try using something else but would love to know what's wrong here.

import subprocess
import random


def makeDir():
    tempDir = random.randrange(111111,999999)
    subprocess.Popen(["mkdir","/mntDir/"+str(tempDir)])
    return tempDir

def mountShare(hostname, username, password):
    mountDir = makeDir()
    try:
        subprocess.Popen(["mount","-t","cifs", "-o",
                      "username="+username+",password="+password,
                      "//"+hostname+"/c$",
                      "/mntDir/"+mountDir])
    except:
        print("Mounting failed")

Asked By: mega_mouse

||

Answers:

This approach has two drawbacks: the first one is that you mount windows share from your webserver. You don;t need to mount it dynamically and in no account you should not mount it for every request. Separate your implementation and infrastructure. Mount required directory in /etc/fstab and let your webserver to rely on existence of the directory.

But then there is another problem: what for you are copying the files to another machine? Do you want to process them there? How you want to notify windows machine that it needs to process data? Why not to run another web server on it and send it requests when you need to process something. And at this point you can drop all that network file systems and send the files within the requests. So you will have linux based frontend server which performs some actions by sending HTTP requests to windows backend server. This will also allow you to notify frontend when the processing is ready.

Answered By: Alexey Guseynov

First, drop the exception block as it hides error details, anyway Popen and other subprocess methods only throw exceptions when they cannot start commands (because of command not found), which means that mount is actually called.

Second, you really don't need Popen, but call (and as a bonus you get the return code directly)

rc = subprocess.call(["mount","-t","cifs", "-o",
                      "username="+username+",password="+password,
                      "//"+hostname+"/c$",
                      "/mntDir/"+mountDir])
if rc:
   print("mount failed")

In your case, the problem is the general exception block.

This method:

def makeDir():
    tempDir = random.randrange(111111,999999)
    subprocess.Popen(["mkdir","/mntDir/"+str(tempDir)])
    return tempDir

returns an integer, so if you remove the exception block you get error because you're adding a string with an integer (TypeError: Can't convert 'int' object to str implicitly). It's a simple mistake that you could have seen if it hadn't for the stupid exception catch which mislead you.

But with the generic try/except block without any argument, you just get mount failed useless message. never protect your statements with try:/except:, that's counter-productive.

If you really want to do that,do this:

try:
    some_command
except Exception as e:
    # print detailed exception, not just "error"
    print("Something went wrong "+str(e))

Now to sum it up, here's a fixed version of your code (with some slight improvements as a bonus):

import subprocess,os
import random


def makeDir():
    # directly create directory name as a string
    tempDir = "/mntDir/{}".format(random.randrange(111111,999999))
    # no need for a subprocess, python handles this well!
    os.mkdir(tempDir)
    # returns the absolute directory name, as string
    return tempDir

def mountShare(hostname, username, password):
    mountDir = makeDir()
    rc = subprocess.call(["mount","-t","cifs", "-o",
                      "username="+username+",password="+password,
                      "//"+hostname+"/c$",
                      mountDir])
    if rc!=0:
        print("Mounting failed")

The solution was not to mount the share but rather to copy on the fly using smbclient. The command I'm using refers to an authfile which contains an account with the relevant permissions in the form:

username = yourUsername
password = yourPassword
domain = yourDomain

The permissions on this file are set to 500.

The smbclient command is then used to create a directory on the remote machine and copy files to that directory.

smbclient //hostname/c$ -A /authfile -c "mkdir someDir; cd someDir/; lcd /folderToCopyFrom; prompt; recurse; mput *; exit;"

Thank you all for the advice, most helpful!

Answered By: mega_mouse

I used the SMBConnection class found in pysmb (https://pythonhosted.org/pysmb/api/smb_SMBConnection.html). Very simple and no need for mounting.

 conn = SMBConnection(user, pw, myname, srv, use_ntlm_v2 = True)
 conn.connect(ip, port=139)
 file2transfer = open(filename,"r")
 conn.storeFile(share,path + filename, file2transfer, timeout=30 )

Make sure that the user has logon rights to the fileshare.

Answered By: erik
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.