public static void ParseSmbConfShareList(SharesList dstList, string smbConfContent) { // note: some smb.conffeatures are not handled. Like special variables and includes. // TODO special case for [homes] share var parser = new IniDataParser(); parser.Configuration.CommentRegex = new System.Text.RegularExpressions.Regex(@"^[#;](.*)"); var iniData = parser.Parse(smbConfContent); foreach (var shareIniSection in iniData.Sections) { string shareName = shareIniSection.SectionName; if (shareName == "global") { continue; } if (!shareIniSection.Keys.ContainsKey("path")) { throw new Exception(String.Format("share {0} doesn't have local path specified", shareName)); } string shareLocalPath = shareIniSection.Keys["path"]; dstList.AddOrReplace(new Share(shareName, new TokenizedLocalPath(shareLocalPath, '/'))); } }
/// <summary> /// Loads the usershare shares. /// </summary> public static void LoadUserShares(SharesList dstList) { // Use "net usershare info" command for easy access to samba shares without root. // These are limited to user shares, so we still need to parse smb.conf for // system-wide shares. // Not that I could read them directly from /var/lib/samba/usershares, but the hope // is that the net command filters some permissions, so I don't have to. Actually // I haven't checked if it does, so it may make sense in order to remove one // dipendency. string output; using (System.Diagnostics.Process p = new System.Diagnostics.Process()) { p.StartInfo.FileName = "net"; p.StartInfo.Arguments = "usershare info"; p.StartInfo.RedirectStandardError = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.UseShellExecute = false; if (!p.Start()) { throw new Exception("could not start command \"net usershare info\" to get list of shares"); } output = p.StandardOutput.ReadToEnd(); p.WaitForExit(); if (p.ExitCode != 0) { throw new Exception(string.Format("command \"net usershare info\" returned error code {0}", p.ExitCode)); } } ParseNetUserShareList(dstList, output); }
public static void LoadGlobalShares(SharesList dstList) { // There are multiple ways actually: // 1. Lounch the "net share" command // 2. Read shares from registry // 3. Call native methods from mpr.dll // 4. System.Management // Side note: actually there's something like Win32_ShareToDirectory in Windows, // but I already have all the nice abstraction around having a share list... using (ManagementClass exportedShares = new ManagementClass("Win32_Share")) { ManagementObjectCollection shares = exportedShares.GetInstances(); List <Share> addWithHigherPriority = new List <Share>(shares.Count); List <Share> addWithLowerPriority = new List <Share>(shares.Count); foreach (ManagementObject share in shares) { // Reference: https://msdn.microsoft.com/en-us/library/aa394435.aspx ShareType type = (ShareType)Convert.ToUInt32(share["Type"]); string shareName = share["Name"].ToString(); string localPath = share["Path"].ToString(); if (type == ShareType.DiskDrive || type == ShareType.Device) { addWithHigherPriority.Add(new Share(shareName, new TokenizedLocalPath(localPath, '\\'))); } else if (type == ShareType.DiskDriveAdmin || type == ShareType.DeviceAdmin) { // Take it, but add it with a lower priority. // This way the explicit shares take priority, as these are usually // the ones the user wants to be used and often have lesser // access restrictions. addWithLowerPriority.Add(new Share(shareName, new TokenizedLocalPath(localPath, '\\'))); } else { // skip everything else } } // First add the shares that should take lower priority. foreach (Share share in addWithLowerPriority) { dstList.AddOrReplace(share); } // Then add the ones with higher priority, overwriting eventual shares. foreach (Share share in addWithHigherPriority) { dstList.AddOrReplace(share); } } }
/// <summary> /// Loads the global samba shares from smb.conf. /// </summary> public static void LoadGlobalSambaShares(SharesList dstList) { string smbConfPath = "/etc/samba/smb.conf"; if (!File.Exists(smbConfPath)) { throw new FileNotFoundException(smbConfPath); } string content = File.ReadAllText(smbConfPath); ParseSmbConfShareList(dstList, content); }
public static void ParseNetUserShareList(SharesList dstList, string shareListIniContent) { var parser = new IniDataParser(); var iniData = parser.Parse(shareListIniContent); foreach (var shareIniSection in iniData.Sections) { string shareName = shareIniSection.SectionName; if (!shareIniSection.Keys.ContainsKey("path")) { throw new Exception(String.Format("share {0} doesn't have local path specified", shareName)); } string shareLocalPath = shareIniSection.Keys["path"]; dstList.AddOrReplace(new Share(shareName, new TokenizedLocalPath(shareLocalPath, '/'))); } }
public static string MakeLink(LinkFormat linkFormat, string host, SharesList shares, TokenizedLocalPath localPath, char systemSeparator) { if (linkFormat == LinkFormat.LocalFile || linkFormat == LinkFormat.LocalPath) { return(ComposeLink(linkFormat, host, null, localPath, systemSeparator)); } else { Share share = shares.FindParentShare(localPath); if (share != null) { TokenizedLocalPath relPath = TokenizedLocalPath.MakeRelative(share.LocalPath, localPath, shares.CaseSensitiveFileSystem); return(ComposeLink(linkFormat, host, share, relPath, systemSeparator)); } else { return(null); } } }
[STAThread] // for Windows.Forms, which is used by clipboard public static int Main(string[] args) { // Failed unless otherwise reached the right point. int exitCode = 1; string host = null; string sharesfile = null; bool showHelp = false; bool showVersion = false; LinkFormat linkFormat = LinkFormat.Unc; HostType hostType = HostType.Netbios; // Not implemented for now to keep things simple. The problem with the clipboard is that it's normally tied to a widget toolkit. // Other than that, easy peasy. bool copyToClipboard = false; bool readStdIn = false; bool noStdErr = false; // I don't really like changing local variables from lambdas, but it makes the code short. // If I get to know other command line parsing libs I may replace Mono.Options in the future.. maybe. var options = new OptionSet() { { "host=", "Custom hostname.", (string v) => host = v }, { "sharesfile=", "Provide a custom .ini file with share list. The format should correspond to outout of \"net usershare info\" command, which also matches smb.conf share list.", (string v) => sharesfile = v }, { "format=", "Link format. Either smb or unc. Default is " + linkFormat + ".", (LinkFormat v) => linkFormat = v }, { "hosttype=", "One of ip, ip6, netbios or hostname. Default is " + hostType + ". Ignored if custom host is specified.", (HostType v) => hostType = v }, { "help", "Show this message and exit.", v => showHelp = v != null }, { "stdin", "Read path list from strandard input.", v => readStdIn = v != null }, { "nostderr", "Write errors into stdout instead of stderr.", v => noStdErr = v != null }, { "copy", "Copy the result to clipboard instead of standard output.", v => copyToClipboard = v != null }, { "version", "Print version and exit.", v => showVersion = v != null }, }; try { List <string> localPaths = options.Parse(args); TextWriter output; StringWriter outputSW; if (copyToClipboard) { outputSW = new StringWriter(); output = outputSW; } else { outputSW = null; output = Console.Out; } if (noStdErr) { Console.SetError(output); } if (readStdIn) { string s; while ((s = Console.In.ReadLine()) != null) { localPaths.Add(s); } } if (showVersion) { Console.WriteLine(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()); exitCode = 0; } else if (showHelp) { exitCode = 0; } else { if (localPaths.Count < 1) { throw new InputError("local path not specified"); } // Make the path searches case insensitive on Windows and // case sensitive on anything else. // This is inexact as on the same machine it's possible to have multiple // file systems with different case sensitivity, but let's keep things // simple. In worst case the link won't be created. bool caseSensitiveFileSystem; switch (Environment.OSVersion.Platform) { case PlatformID.Win32NT: caseSensitiveFileSystem = false; break; case PlatformID.Win32Windows: caseSensitiveFileSystem = false; break; case PlatformID.WinCE: caseSensitiveFileSystem = false; break; default: caseSensitiveFileSystem = true; break; } SharesList shares = new SharesList(caseSensitiveFileSystem); if (sharesfile == null) { switch (Environment.OSVersion.Platform) { case PlatformID.Unix: // Load the samba shares. SambaShareLoader.LoadUserShares(shares); SambaShareLoader.LoadGlobalSambaShares(shares); break; // case PlatformID.MacOSX: case PlatformID.Win32NT: WindowsShareLoader.LoadGlobalShares(shares); break; case PlatformID.Win32Windows: // will it work? WindowsShareLoader.LoadGlobalShares(shares); break; case PlatformID.WinCE: // will it work? WindowsShareLoader.LoadGlobalShares(shares); break; default: throw new Exception(string.Format("Unknown platform {0}. Could not get LAN shares from the system.", Environment.OSVersion.Platform)); } } else { // For the custom share list we use the same format as the "net usershare info" command. string shareListIniContent = File.ReadAllText(sharesfile, Encoding.UTF8); SambaShareLoader.ParseNetUserShareList(shares, shareListIniContent); } if (host == null) { if (hostType == HostType.Ip || hostType == HostType.Ip6) { host = FindIPAddress(hostType == HostType.Ip6); } else if (hostType == HostType.Netbios) { host = System.Environment.MachineName; } else if (hostType == HostType.Hostname) { host = Dns.GetHostName(); } else { throw new InputError(String.Format("unexpected host-type option: {0}", hostType)); } } /* * Small note on complexity. For now the complexity is exponential. * As long as there aren't many shares AND many paths to convert it * shouldn't matter much. Otherwise a better solution is needed with * some data structure. For example searching in a sorted list of * shares using binary search. * * Could implement a binary search on SortedList<string,string> or * use a ready made class prefixdictionary/trie. */ bool first = true; bool foundAll = true; foreach (string localPath in localPaths) { if (first) { first = false; } else { output.WriteLine(); } string link = null; TokenizedLocalPath tokenizedLocalPath = null; try { // Do some sanity checks and make the path absolute. string localAbsolutePath; { // Uri uri = null; // if (Uri.TryCreate(localPath, UriKind.RelativeOrAbsolute, out uri)) { // if (!uri.IsFile) // throw new ArgumentException(string.Format("Could not resolve path {0} to URI. Only file/directory paths are supported.", localPath)); // // // Specific case for URIs like file://host/..., but I haven't seen them in the wild. // if (uri.Host != null && uri.Host != "") // throw new ArgumentException(string.Format("Could not resolve path {0} to URI. Only file/directory paths are supported.", localPath)); // // // replace the path with the absolute one taken from the URI // localAbsolutePath = uri.LocalPath; // } else { // use the passed path as-is localAbsolutePath = localPath; // } } tokenizedLocalPath = new TokenizedLocalPath(localAbsolutePath, Path.DirectorySeparatorChar); } catch (Exception ex) { Console.Error.WriteLine(ex.ToString()); } if (tokenizedLocalPath != null) { link = LinkMaker.MakeLink(linkFormat, host, shares, tokenizedLocalPath, Path.DirectorySeparatorChar); } else { link = localPath; } if (link != null) { output.Write(link); } else { Console.Error.WriteLine(string.Format("No share found containing local path \"{0}\"", localPath)); output.Write(localPath); foundAll = false; } } if (copyToClipboard) { string text = outputSW.ToString(); ClipboardHelper.CopyText(text); } if (foundAll) { // OK. If we reached this point then everything went O~K. exitCode = 0; } } } catch (InputError e) { Console.Error.WriteLine(e.Message); showHelp = true; } catch (OptionException e) { Console.Error.WriteLine(e.ToString()); showHelp = true; } catch (Exception ex) { Console.Error.WriteLine("Unexpected error: " + ex.ToString()); } if (showHelp) { Console.WriteLine("Usage:"); Console.WriteLine("\t" + Path.GetFileNameWithoutExtension(System.Environment.CommandLine) + " <local-path> [<local-path2>, ...]"); Console.WriteLine(); Console.WriteLine("Options:"); options.WriteOptionDescriptions(Console.Out); } return(exitCode); }