Friday, November 13, 2015

What Happens when we don't read the manual

This is RTFD kind of a pun which happened with me. Shooting self in the foot is more like it. I was trying to play around with ctypes and was trying to figure out how to get the last error code. I didn't read the python docs man page till this point and kept doing

#----------------python3 so don't be amazed when we do conversions-----------------------
import ctypes
import ctypes.util
libc = ctypes.util.find_library("c")
libc.prinf("Hello world") # this is not going to print nothing
errno = libc.errno
print( "Error code is " + str(errno))

#------------------------------

Guess what I get , a SIGSEGV .. !!!!! WOW and I now become more bold and try to get deeper with gdb. So here is how I prove myself to be a member of all idiotic morons society's prime member

 gdb python3
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1

Reading symbols from python3...Reading symbols from /usr/lib/debug//usr/bin/python3.4...done. #download the python symbols for this
done.
(gdb) run
Starting program: /usr/bin/python3
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Python 3.4.3 (default, Oct 14 2015, 20:28:29)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> import ctypes.util
>>> libc = ctypes.util.find_library("c")
>>> libc.printf("Hello World")
>>> libc = ctypes.CDLL(libc)
>>> libc.printf("Hello World")
1
H>>> libc.errno
<_FuncPtr object at 0x7ffff5e58688>
>>> libc.errno()

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7fd16a0 in ?? ()
(gdb) bt 5
#0  0x00007ffff7fd16a0 in ?? () <== null function
#1  0x00007ffff5f49adc in ffi_call_unix64 () from /usr/lib/x86_64-linux-gnu/libffi.so.6
#2  0x00007ffff5f4940c in ffi_call () from /usr/lib/x86_64-linux-gnu/libffi.so.6
#3  0x00007ffff6159fed in _call_function_pointer (argcount=0, resmem=0x7fffffffdaf0, restype=<optimized out>, atypes=<optimized out>, avalues=0x7fffffffdae0,
    pProc=0x7ffff7fd16a0, flags=4353) at /build/python3.4-eNHHUi/python3.4-3.4.3/Modules/_ctypes/callproc.c:811
#4  _ctypes_callproc (pProc=pProc@entry=0x7ffff7fd16a0, argtuple=argtuple@entry=(), flags=4353, argtypes=argtypes@entry=0x0,
    restype=<_ctypes.PyCSimpleType at remote 0xa99178>, checker=0x0) at /build/python3.4-eNHHUi/python3.4-3.4.3/Modules/_ctypes/callproc.c:1149
(More stack frames follow...)
(gdb)




finally reading through the documentation the appropriate way of reading the error number is :



Python 3.4.3 (default, Oct 14 2015, 20:28:29)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> import ctypes.util
>>> libc = ctypes.util.find_library("c")
>>> libc = ctypes.CDLL(libc)
>>> libc.printf("Hello World")
>>> ctypes.get_errno()




mount and umount with ctypes

Automating a linux build I came across a phase where we had to do mount. A simple way was to perform a mount command using the subprocess module as shown in the code snippet below

#------------------------
import subprocess

mountSource =  "/home/addy/workspace/pymount/test1"
mountTarget =   "/home/addy/workspace/pymount/test2"
fstype = "ext4"

command = "mount -B -t {2} {0} {1}".format(mountSource, mountTarget,fstype) 

print(command.split(" "))
output = subprocess.check_output(command.split(" "))

#-------------------------

or we could have used ctypes. Using python3 the challenge was to make ctypes work. Python3 strings are unicode by default. We need to convert them to ASCII for linux system calls via the ctypes library.

Although this was an easy task once we understand the intrinsics. We should use encode method of the strings to convert them to the ascii strings.

Following code demonstrates the mount from one directory to another.

#-------------------------
import ctypes
import ctypes.util
import os

mountSource =  "/home/addy/workspace/pymount/test3".encode(encoding='ascii', errors='replace')
mountTarget =   "/home/addy/workspace/pymount/test4".encode(encoding='ascii', errors='replace')

libcPath = ctypes.util.find_library("c")
libc = ctypes.CDLL(libcPath)

retCode = libc.mount(mountSource,mountTarget,None,4096,None)
if retCode != 0:
    #errNumber = libc.errno()
    #print ("Error number from libc " + errNumber)
    print ("os.ErrNo = " + str(os.error() ))

print("ls the mountTarget " + str(mountTarget))
output = os.system("ls -al " + mountTarget.decode("utf-8","ignore"))

errorCode = libc.umount(mountTarget,None)
print("Error Code for umount" + str(errorCode))
if errorCode != 0:
    print("Unmount unsuccessful " + str(libc.errno))
else:
    print("successful unmount")

print("unmount")


#------------------------------------------

A Full class implementation although not tested yet 

#----------------------------------------

import ctypes
import ctypes.util
import os
import pwd

class Mounter(object): 
    """
    sys/mount.h constants
    """
    MS_RDONLY = 1       
    MS_NOSUID = 2      
    MS_NODEV = 4          
    MS_NOEXEC = 8      
    MS_SYNCHRONOUS = 16       
    MS_REMOUNT = 32       
    MS_MANDLOCK = 64       
    MS_DIRSYNC = 128       
    MS_NOATIME = 1024       
    MS_NODIRATIME = 2048       
    MS_BIND = 4096       
    MS_MOVE = 8192
    MS_REC = 16384
    MS_SILENT = 32768
    MS_POSIXACL = 1 << 16   
    MS_UNBINDABLE = 1 << 17   
    MS_PRIVATE = 1 << 18     
    MS_SLAVE = 1 << 19       
    MS_SHARED = 1 << 20       
    MS_RELATIME = 1 << 21   
    MS_KERNMOUNT = 1 << 22   
    MS_I_VERSION =  1 << 23   
    MS_STRICTATIME = 1 << 24   
    MS_ACTIVE = 1 << 30
    MS_NOUSER = 1 << 31
   
   
   
    def __init__(self,sourcePath,destPath,fileSystem = None,flags=Mounter.MS_RDONLY):
       
        if fileSystem != None:
            fslist = []
            with open('/proc/filesystems') as filesystems:
                for line in filesystems:
                    fs = line.split("\t")[1]
                    fslist.append(fs)
        if not fileSystem in fslist:
            raise ValueError([fileSystem," doesn't exist in filesystem list ",fslist])
               
        if not os.access(sourcePath,os.R_OK) :
            raise ValueError(["Cannot access " , sourcePath])
        if not os.access(destPath,os.W_OK):
            raise ValueError(["Cannot access " , destPath])
       
        #since we have reached here
        self.fileSystem = fileSystem
        self.libc = ctypes.CDLL(self.get_libc())
        self.mountSource = sourcePath
        self.mountTarget = destPath
        self.flags = flags
       
       
    def get_libc(self):
        libcPath = ctypes.util.find_library("c")
        return libcPath
   
    def is_current_user_root(self):
        pwDBEntry = pwd.getpwuid(os.getuid())
        userName = pwDBEntry.pw_name
        if userName == "root":
            return True
        else:
            return False
           
    def mount_path(self):
        mountPathBytes = self.mountSource.encode("ascii","ignore")
        targetPathBytes = self.mountTarget.encode("ascii","ignore")
        fileSystemBytes = self.fileSystem.encode("ascii","ignore")
       
        errorCode = self.libc.mount(mountPathBytes,
                                    targetPathBytes,
                                    fileSystemBytes,
                                    self.flags,None) #extra data is considered null
       
        if errorCode != 0:
            osErrorCode = ctypes.get_errno()
            osErrorString = os.errno.errorcode[osErrorCode]
            raise OSError([osErrorCode, osErrorString])
       
   
    def unmount_path(self,flags=None):
        targetPathBytes = self.mountTarget.encode("ascii","ignore")
        errorCode = self.libc.umount(targetPathBytes,flags)
        if errorCode != 0:
            osErrorCode = ctypes.get_errno()
            osErrorString = os.errno.errorcode[osErrorCode]
            raise OSError([osErrorCode, osErrorString])

#-----------------------------------------------------