smbfs.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. package smbfs
  2. import (
  3. "fmt"
  4. "io"
  5. "io/fs"
  6. "log"
  7. "net"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "strings"
  12. "time"
  13. "github.com/hirochachacha/go-smb2"
  14. "imuslab.com/arozos/mod/filesystem/arozfs"
  15. )
  16. /*
  17. Server Message Block.go
  18. This is a file abstraction that mount SMB folders onto ArozOS as virtual drive
  19. */
  20. type ServerMessageBlockFileSystemAbstraction struct {
  21. UUID string
  22. Hierarchy string
  23. root string
  24. ipaddr string
  25. user string
  26. pass string
  27. conn *net.Conn
  28. session *smb2.Session
  29. share *smb2.Share
  30. tickerChan chan bool
  31. }
  32. func NewServerMessageBlockFileSystemAbstraction(uuid string, hierarchy string, ipaddr string, rootShare string, username string, password string) (ServerMessageBlockFileSystemAbstraction, error) {
  33. log.Println("[SMB-FS] Connecting to " + uuid + ":/ (" + ipaddr + ")")
  34. //Patch the ip address if port not found
  35. if !strings.Contains(ipaddr, ":") {
  36. log.Println("[SMB-FS] Port not set. Using default SMB port (:445)")
  37. ipaddr = ipaddr + ":445" //Default port for SMB
  38. }
  39. nd := net.Dialer{Timeout: 10 * time.Second}
  40. conn, err := nd.Dial("tcp", ipaddr)
  41. if err != nil {
  42. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  43. return ServerMessageBlockFileSystemAbstraction{}, err
  44. }
  45. d := &smb2.Dialer{
  46. Initiator: &smb2.NTLMInitiator{
  47. User: username,
  48. Password: password,
  49. },
  50. }
  51. s, err := d.Dial(conn)
  52. if err != nil {
  53. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  54. return ServerMessageBlockFileSystemAbstraction{}, err
  55. }
  56. //Mound remote storage
  57. fs, err := s.Mount(rootShare)
  58. if err != nil {
  59. log.Println("[SMB-FS] Unable to connect to remote: ", err.Error())
  60. return ServerMessageBlockFileSystemAbstraction{}, err
  61. }
  62. done := make(chan bool)
  63. fsAbstraction := ServerMessageBlockFileSystemAbstraction{
  64. UUID: uuid,
  65. Hierarchy: hierarchy,
  66. root: rootShare,
  67. ipaddr: ipaddr,
  68. user: username,
  69. pass: password,
  70. conn: &conn,
  71. session: s,
  72. share: fs,
  73. tickerChan: done,
  74. }
  75. return fsAbstraction, nil
  76. }
  77. func (a ServerMessageBlockFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
  78. filename = filterFilepath(filename)
  79. filename = toWinPath(filename)
  80. return a.share.Chmod(filename, mode)
  81. }
  82. func (a ServerMessageBlockFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
  83. return arozfs.ErrOperationNotSupported
  84. }
  85. func (a ServerMessageBlockFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  86. filename = filterFilepath(filename)
  87. filename = toWinPath(filename)
  88. return a.share.Chtimes(filename, atime, mtime)
  89. }
  90. func (a ServerMessageBlockFileSystemAbstraction) Create(filename string) (arozfs.File, error) {
  91. filename = filterFilepath(filename)
  92. f, err := a.share.Create(filename)
  93. if err != nil {
  94. return nil, err
  95. }
  96. af := NewSmbFsFile(f)
  97. return af, nil
  98. }
  99. func (a ServerMessageBlockFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
  100. filename = filterFilepath(filename)
  101. filename = toWinPath(filename)
  102. return a.share.Mkdir(filename, mode)
  103. }
  104. func (a ServerMessageBlockFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
  105. filename = filterFilepath(filename)
  106. filename = toWinPath(filename)
  107. return a.share.MkdirAll(filename, mode)
  108. }
  109. func (a ServerMessageBlockFileSystemAbstraction) Name() string {
  110. return ""
  111. }
  112. func (a ServerMessageBlockFileSystemAbstraction) Open(filename string) (arozfs.File, error) {
  113. filename = toWinPath(filterFilepath(filename))
  114. f, err := a.share.Open(filename)
  115. if err != nil {
  116. return nil, err
  117. }
  118. af := NewSmbFsFile(f)
  119. return af, nil
  120. }
  121. func (a ServerMessageBlockFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
  122. filename = toWinPath(filterFilepath(filename))
  123. f, err := a.share.OpenFile(filename, flag, perm)
  124. if err != nil {
  125. return nil, err
  126. }
  127. af := NewSmbFsFile(f)
  128. return af, nil
  129. }
  130. func (a ServerMessageBlockFileSystemAbstraction) Remove(filename string) error {
  131. filename = filterFilepath(filename)
  132. filename = toWinPath(filename)
  133. return a.share.Remove(filename)
  134. }
  135. func (a ServerMessageBlockFileSystemAbstraction) RemoveAll(filename string) error {
  136. filename = filterFilepath(filename)
  137. filename = toWinPath(filename)
  138. return a.share.RemoveAll(filename)
  139. }
  140. func (a ServerMessageBlockFileSystemAbstraction) Rename(oldname, newname string) error {
  141. oldname = toWinPath(filterFilepath(oldname))
  142. newname = toWinPath(filterFilepath(newname))
  143. return a.share.Rename(oldname, newname)
  144. }
  145. func (a ServerMessageBlockFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
  146. filename = toWinPath(filterFilepath(filename))
  147. return a.share.Stat(filename)
  148. }
  149. func (a ServerMessageBlockFileSystemAbstraction) Close() error {
  150. //Stop connection checker
  151. go func() {
  152. a.tickerChan <- true
  153. }()
  154. //Unmount the smb folder
  155. time.Sleep(300 * time.Millisecond)
  156. a.share.Umount()
  157. time.Sleep(300 * time.Millisecond)
  158. a.session.Logoff()
  159. time.Sleep(300 * time.Millisecond)
  160. conn := *(a.conn)
  161. conn.Close()
  162. time.Sleep(500 * time.Millisecond)
  163. return nil
  164. }
  165. /*
  166. Abstraction Utilities
  167. */
  168. func (a ServerMessageBlockFileSystemAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
  169. if strings.HasPrefix(subpath, a.UUID+":") {
  170. //This is full virtual path. Trim the uuid and correct the subpath
  171. subpath = strings.TrimPrefix(subpath, a.UUID+":")
  172. }
  173. subpath = filterFilepath(subpath)
  174. if a.Hierarchy == "user" {
  175. return toWinPath(filepath.ToSlash(filepath.Clean(filepath.Join("users", username, subpath)))), nil
  176. } else if a.Hierarchy == "public" {
  177. return toWinPath(filepath.ToSlash(filepath.Clean(subpath))), nil
  178. }
  179. return "", arozfs.ErrVpathResolveFailed
  180. }
  181. func (a ServerMessageBlockFileSystemAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
  182. fullpath = filterFilepath(fullpath)
  183. fullpath = strings.TrimPrefix(fullpath, "\\")
  184. vpath := a.UUID + ":/" + strings.ReplaceAll(fullpath, "\\", "/")
  185. return vpath, nil
  186. }
  187. func (a ServerMessageBlockFileSystemAbstraction) FileExists(realpath string) bool {
  188. realpath = toWinPath(filterFilepath(realpath))
  189. f, err := a.share.Open(realpath)
  190. if err != nil {
  191. return false
  192. }
  193. f.Close()
  194. return true
  195. }
  196. func (a ServerMessageBlockFileSystemAbstraction) IsDir(realpath string) bool {
  197. realpath = filterFilepath(realpath)
  198. realpath = toWinPath(realpath)
  199. stx, err := a.share.Stat(realpath)
  200. if err != nil {
  201. return false
  202. }
  203. return stx.IsDir()
  204. }
  205. func (a ServerMessageBlockFileSystemAbstraction) Glob(realpathWildcard string) ([]string, error) {
  206. realpathWildcard = strings.ReplaceAll(realpathWildcard, "[", "?")
  207. realpathWildcard = strings.ReplaceAll(realpathWildcard, "]", "?")
  208. matches, err := a.share.Glob(realpathWildcard)
  209. if err != nil {
  210. return []string{}, err
  211. }
  212. return matches, nil
  213. }
  214. func (a ServerMessageBlockFileSystemAbstraction) GetFileSize(realpath string) int64 {
  215. realpath = toWinPath(filterFilepath(realpath))
  216. stat, err := a.share.Stat(realpath)
  217. if err != nil {
  218. return 0
  219. }
  220. return stat.Size()
  221. }
  222. func (a ServerMessageBlockFileSystemAbstraction) GetModTime(realpath string) (int64, error) {
  223. realpath = toWinPath(filterFilepath(realpath))
  224. stat, err := a.share.Stat(realpath)
  225. if err != nil {
  226. return 0, nil
  227. }
  228. return stat.ModTime().Unix(), nil
  229. }
  230. func (a ServerMessageBlockFileSystemAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
  231. filename = toWinPath(filterFilepath(filename))
  232. return a.share.WriteFile(filename, content, mode)
  233. }
  234. func (a ServerMessageBlockFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
  235. filename = toWinPath(filterFilepath(filename))
  236. return a.share.ReadFile(filename)
  237. }
  238. func (a ServerMessageBlockFileSystemAbstraction) ReadDir(filename string) ([]fs.DirEntry, error) {
  239. filename = toWinPath(filterFilepath(filename))
  240. fis, err := a.share.ReadDir(filename)
  241. if err != nil {
  242. return []fs.DirEntry{}, err
  243. }
  244. dirEntires := []fs.DirEntry{}
  245. for _, fi := range fis {
  246. if fi.Name() == "System Volume Information" || fi.Name() == "$RECYCLE.BIN" || fi.Name() == "$MFT" {
  247. //System folders. Hide it
  248. continue
  249. }
  250. dirEntires = append(dirEntires, newDirEntryFromFileInfo(fi))
  251. }
  252. return dirEntires, nil
  253. }
  254. func (a ServerMessageBlockFileSystemAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  255. filename = toWinPath(filterFilepath(filename))
  256. f, err := a.share.OpenFile(filename, os.O_CREATE|os.O_WRONLY, mode)
  257. if err != nil {
  258. return err
  259. }
  260. p := make([]byte, 32768)
  261. for {
  262. _, err := stream.Read(p)
  263. if err != nil {
  264. if err == io.EOF {
  265. break
  266. } else {
  267. return err
  268. }
  269. }
  270. _, err = f.Write(p)
  271. if err != nil {
  272. return err
  273. }
  274. }
  275. return nil
  276. }
  277. func (a ServerMessageBlockFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
  278. filename = toWinPath(filterFilepath(filename))
  279. f, err := a.share.OpenFile(filename, os.O_RDONLY, 0755)
  280. if err != nil {
  281. return nil, err
  282. }
  283. return f, nil
  284. }
  285. //Note that walk on SMB is super slow. Avoid using this if possible.
  286. func (a ServerMessageBlockFileSystemAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
  287. root = toWinPath(filterFilepath(root))
  288. err := fs.WalkDir(a.share.DirFS(root), ".", func(path string, d fs.DirEntry, err error) error {
  289. if err != nil {
  290. return err
  291. }
  292. statInfo, err := d.Info()
  293. if err != nil {
  294. return err
  295. }
  296. walkFn(filepath.ToSlash(filepath.Join(root, path)), statInfo, err)
  297. return nil
  298. })
  299. return err
  300. }
  301. func (a ServerMessageBlockFileSystemAbstraction) Heartbeat() error {
  302. _, err := a.share.Stat("")
  303. return err
  304. }
  305. /*
  306. Optional Functions
  307. */
  308. func (a *ServerMessageBlockFileSystemAbstraction) CapacityInfo() {
  309. fsinfo, err := a.share.Statfs(".")
  310. if err != nil {
  311. return
  312. }
  313. fmt.Println(fsinfo)
  314. }
  315. /*
  316. Helper Functions
  317. */
  318. func toWinPath(filename string) string {
  319. backslashed := strings.ReplaceAll(filename, "/", "\\")
  320. return strings.TrimPrefix(backslashed, "\\")
  321. }
  322. func filterFilepath(rawpath string) string {
  323. rawpath = filepath.ToSlash(filepath.Clean(rawpath))
  324. rawpath = strings.TrimSpace(rawpath)
  325. if strings.HasPrefix(rawpath, "./") {
  326. return rawpath[1:]
  327. } else if rawpath == "." || rawpath == "" {
  328. return "/"
  329. }
  330. return rawpath
  331. }
  332. func wildCardToRegexp(pattern string) string {
  333. var result strings.Builder
  334. for i, literal := range strings.Split(pattern, "*") {
  335. // Replace * with .*
  336. if i > 0 {
  337. result.WriteString(".*")
  338. }
  339. // Quote any regular expression meta characters in the
  340. // literal text.
  341. result.WriteString(regexp.QuoteMeta(literal))
  342. }
  343. return result.String()
  344. }