1 module libssh.sftp;
2 
3 import core.stdc..string : memcpy;
4 
5 import libssh.c_bindings.libssh;
6 import libssh.c_bindings.sftp;
7 import libssh.c_bindings.ctypes;
8 import libssh.errors;
9 import libssh.utils;
10 import libssh.session;
11 
12 
13 struct SFTPStatVFS {
14     // just copy sftp_statvfs_struct
15     ulong f_bsize;   /** file system block size */
16     ulong f_frsize;  /** fundamental fs block size */
17     ulong f_blocks;  /** number of blocks (unit f_frsize) */
18     ulong f_bfree;   /** free blocks in file system */
19     ulong f_bavail;  /** free blocks for non-root */
20     ulong f_files;   /** total file inodes */
21     ulong f_ffree;   /** free file inodes */
22     ulong f_favail;  /** free file inodes for to non-root */
23     ulong f_fsid;    /** file system id */
24     ulong f_flag;    /** bit mask of f_flag values */
25     ulong f_namemax; /** maximum filename length */
26 }
27 
28 struct TimesValue {
29     long sec;
30     long usec;
31 }
32 
33 struct SFTPAttributes {
34     string name;
35     string longName; /* ls -l output on openssh, not reliable else */
36     uint flags;     // TODO: enum
37     ubyte type;
38     ulong size;
39     uint uid;
40     uint gid;
41     string owner; /* set if openssh and version 4 */
42     string group; /* set if openssh and version 4 */
43     uint permissions;
44     ulong atime64;
45     uint atime;
46     uint atimeNSeconds;
47     ulong createtime;
48     uint createtimeNSeconds;
49     ulong mtime64;
50     uint mtime;
51     uint mtimeNSeconds;
52     string acl;
53     uint extendedCount;
54     string extendedType;
55     string extendedData;
56 }
57 
58 
59 class SFTPDirectory : Disposable {
60     @property bool eof() {
61         return sftp_dir_eof(this._dir) == 0 ? false : true;
62     }
63 
64     void readdir(out SFTPAttributes attrs) {
65         auto result = sftp_readdir(this._session._sftpSession, this._dir);
66         if (result is null) {
67             throw new SFTPException(sftp_get_error(this._session._sftpSession),
68                 this._session._session._session);
69         }
70         convertAndFreeSftpAttributes(result, attrs);
71     }
72 
73     void close() {
74         auto rc = sftp_closedir(this._dir);
75         if (rc != SSH_OK) {
76             throw new SFTPException(sftp_get_error(this._session._sftpSession),
77                 this._session._session._session);
78         }
79         this._dir = null;
80         this._session = null;
81     }
82 
83     ~this() {
84         this._dispose(true);
85     }
86 
87     override void dispose() {
88         this._dispose(false);
89     }
90 
91     private {
92         void _dispose(bool fromDtor) {
93             if (this._dir !is null) {
94                 sftp_closedir(this._dir);
95                 this._dir = null;
96                 this._session = null;
97             }
98         }
99 
100         this(SFTPSession session, sftp_dir dir) {
101             this._session = session;
102             this._dir = dir;
103         }
104 
105         SFTPSession _session;
106         sftp_dir _dir;
107     }
108 }
109 
110 class SFTPFile : Disposable {
111     enum auto ReadAgain = int.min;
112     enum auto WriteAgain = int.min;
113 
114     @property void blocking(bool v) {
115         if (v) {
116             sftp_file_set_blocking(this._file);
117         } else {
118             sftp_file_set_nonblocking(this._file);
119         }
120     }
121 
122     uint asyncReadBegin(uint len) {
123         auto rc = sftp_async_read_begin(this._file, len);
124         if (rc < 0) {
125             throw new SFTPException(sftp_get_error(this._session._sftpSession),
126                 this._session._session._session);
127         }
128         return cast(uint) rc;
129     }
130     
131     /**
132      * returns SFTPFile.ReadAgain in nonblocking mode
133      **/
134     int asyncRead(void[] buffer, uint id) {
135         auto rc = sftp_async_read(this._file, buffer.ptr, cast(uint) buffer.length, id);
136         if (rc == SSH_AGAIN) {
137             return ReadAgain;
138         }
139         if (rc < 0) {
140             throw new SFTPException(sftp_get_error(this._session._sftpSession),
141                 this._session._session._session);
142         }
143         return rc;
144     }
145 
146     /**
147      * returns SFTPFile.ReadAgain in nonblocking mode
148      **/
149     size_t read(void[] buffer) {
150         auto rc = sftp_read(this._file, buffer.ptr, cast(size_t) buffer.length);
151         if (rc == SSH_AGAIN) {
152             return ReadAgain;
153         }
154         if (rc < 0) {
155             throw new SFTPException(sftp_get_error(this._session._sftpSession),
156                 this._session._session._session);
157         }
158         return rc;
159     }
160 
161     /**
162      * returns SFTPFile.WriteAgain in nonblocking mode
163      **/
164     size_t write(const void[] buffer) {
165         auto rc = sftp_write(this._file, buffer.ptr, cast(size_t) buffer.length);
166         if (rc == SSH_AGAIN) {
167             return WriteAgain;
168         }
169         if (rc < 0) {
170             throw new SFTPException(sftp_get_error(this._session._sftpSession),
171                 this._session._session._session);
172         }
173         return rc;
174     }
175 
176     void rewind() {
177         sftp_rewind(this._file);
178     }
179 
180     void seek(uint newOffset) {
181         auto rc = sftp_seek(this._file, newOffset);
182         if (rc != SSH_OK) {
183             throw new SFTPException(sftp_get_error(this._session._sftpSession),
184                 this._session._session._session);
185         }
186     }
187 
188     uint tell() {
189         auto rc = sftp_tell(this._file);
190         if (rc < 0) {
191             throw new SFTPException(sftp_get_error(this._session._sftpSession),
192                 this._session._session._session);
193         }
194         return rc;
195     }
196 
197     ulong tell64() {
198         auto rc = sftp_tell64(this._file);
199         if (rc < 0) {
200             throw new SFTPException(sftp_get_error(this._session._sftpSession),
201                 this._session._session._session);
202         }
203         return rc;
204     }
205 
206     void seek64(ulong newOffset) {
207         auto rc = sftp_seek64(this._file, newOffset);
208         if (rc != SSH_OK) {
209             throw new SFTPException(sftp_get_error(this._session._sftpSession),
210                 this._session._session._session);
211         }
212     }
213 
214     void fstat(out SFTPAttributes attrs) {
215         auto result = sftp_fstat(this._file);
216         if (result is null) {
217             throw new SFTPException(sftp_get_error(this._session._sftpSession),
218                 this._session._session._session);
219         }
220         convertAndFreeSftpAttributes(result, attrs);
221     }
222 
223     SFTPStatVFS fstatVFS() {
224         auto result = sftp_fstatvfs(this._file);
225         if (result is null) {
226             throw new SFTPException(sftp_get_error(this._session._sftpSession),
227                 this._session._session._session);
228         }
229         scope(exit) sftp_statvfs_free(result);
230 
231         SFTPStatVFS resultObj;
232         memcpy(&resultObj, result, SFTPStatVFS.sizeof);
233         return resultObj;
234     }
235 
236     void close() {
237         auto rc = sftp_close(this._file);
238         if (rc != SSH_OK) {
239             throw new SFTPException(sftp_get_error(this._session._sftpSession),
240                 this._session._session._session);
241         }
242         this._file = null;
243     }
244 
245     ~this() {
246         this._dispose(true);
247     }
248 
249     override void dispose() {
250         this._dispose(false);
251     }
252 
253     private {
254         this(SFTPSession session, sftp_file file) {
255             this._session = session;
256             this._file = file;
257         }
258 
259         void _dispose(bool fromDtor) {
260             if (this._file !is null) {
261                 sftp_close(this._file);
262                 this._file = null;
263                 this._session = null;
264             }
265         }
266 
267         SFTPSession _session;
268         sftp_file _file;
269     }
270 }
271 
272 class SFTPSession : Disposable {
273     @property uint extensionsCount() {
274         return sftp_extensions_get_count(this._sftpSession);
275     }
276 
277     @property int serverVersion() {
278         return sftp_server_version(this._sftpSession);
279     }
280 
281     string canonicalizePath(string path) {
282         auto result = sftp_canonicalize_path(this._sftpSession, toStrZ(path));
283         if (result is null) {
284             throw new SFTPException(SFTPError.Unknown, this._session._session);
285         }
286         scope(exit) ssh_string_free_char(result);
287         return copyFromStrZ(result);
288     }
289 
290     bool isExtensionSupported(string name, string data) {
291         return sftp_extension_supported(this._sftpSession, toStrZ(name), toStrZ(data)) == 0 ? 
292             false : true;
293     }
294 
295     string getExtensionData(uint index) {
296         return fromStrZ(sftp_extensions_get_data(this._sftpSession, index));
297     }
298 
299     string getExtensionName(uint index) {
300         return fromStrZ(sftp_extensions_get_name(this._sftpSession, index));
301     }
302 
303 
304     // TODO: accessType to flags
305     // TODO: mode consts
306     SFTPFile open(string file, int accessType, uint mode) {
307         auto result = sftp_open(this._sftpSession, toStrZ(file), accessType, cast(mode_t) mode);
308         if (result is null) {
309             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
310         }
311         return new SFTPFile(this, result);
312     }
313 
314     SFTPDirectory openDir(string path) {
315         auto result = sftp_opendir(this._sftpSession, toStrZ(path));
316         if (result is null) {
317             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
318         }
319         return new SFTPDirectory(this, result);
320     }
321 
322     void chmod(string file, uint mode) {
323         auto rc = sftp_chmod(this._sftpSession, toStrZ(file), cast(mode_t) mode);
324         if (rc != SSH_OK) {
325             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
326         }
327     }
328 
329     void chown(string file, uint uid, uint gid) {
330         auto rc = sftp_chown(this._sftpSession, toStrZ(file), cast(uid_t) uid, cast(gid_t) gid);
331         if (rc != SSH_OK) {
332             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
333         }
334     }
335 
336     void mkdir(string dir, uint mode) {
337         auto rc = sftp_mkdir(this._sftpSession, toStrZ(dir), cast(mode_t) mode);
338         if (rc != SSH_OK) {
339             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
340         }
341     }
342 
343     string readlink(string path) {
344         auto result = sftp_readlink(this._sftpSession, toStrZ(path));
345         if (result is null) {
346             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
347         }
348         scope(exit) ssh_string_free_char(result);
349         return copyFromStrZ(result);
350     }
351 
352     void symlink(string target, string dest) {
353         auto rc = sftp_symlink(this._sftpSession, toStrZ(target), toStrZ(dest));
354         if (rc != SSH_OK) {
355             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
356         }
357     }
358 
359     void rename(string original, string newName) {
360         auto rc = sftp_rename(this._sftpSession, toStrZ(original), toStrZ(newName));
361         if (rc != SSH_OK) {
362             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
363         }
364     }
365 
366     void unlink(string path) {
367         auto rc = sftp_unlink(this._sftpSession, toStrZ(path));
368         if (rc != SSH_OK) {
369             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
370         }
371     }
372 
373     void rmdir(string path) {
374         auto rc = sftp_rmdir(this._sftpSession, toStrZ(path));
375         if (rc != SSH_OK) {
376             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
377         }
378     }
379 
380     void stat(string path, out SFTPAttributes attrs) {
381         auto result = sftp_stat(this._sftpSession, toStrZ(path));
382         if (result is null) {
383             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
384         }
385         convertAndFreeSftpAttributes(result, attrs);
386     }
387 
388     void lstat(string path, out SFTPAttributes attrs) {
389         auto result = sftp_lstat(this._sftpSession, toStrZ(path));
390         if (result is null) {
391             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
392         }
393         convertAndFreeSftpAttributes(result, attrs);
394     }
395 
396     void setStat(string path, SFTPAttributes attrs) {
397         sftp_attributes_struct attrsStruct;
398         convertAndSetSftpAttributes(&attrsStruct, attrs);
399         scope(exit) {
400             if (attrsStruct.extended_data !is null) {
401                 ssh_string_free(attrsStruct.extended_data);
402             }
403             if (attrsStruct.extended_type !is null) {
404                 ssh_string_free(attrsStruct.extended_type);
405             }
406         }
407         auto rc = sftp_setstat(this._sftpSession, toStrZ(path), &attrsStruct);
408         if (rc != SSH_OK) {
409             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
410         }
411     }
412 
413     SFTPStatVFS statVFS(string path) {
414         auto result = sftp_statvfs(this._sftpSession, toStrZ(path));
415         if (result is null) {
416             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
417         }
418         scope(exit) sftp_statvfs_free(result);
419         
420         SFTPStatVFS resultObj;
421         memcpy(&resultObj, result, SFTPStatVFS.sizeof);
422         return resultObj;
423     }
424 
425     TimesValue utimes(string file) {
426         timeval result;
427         auto rc = sftp_utimes(this._sftpSession, toStrZ(file), &result);
428         if (rc != SSH_OK) {
429             throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
430         }
431 
432         TimesValue resulObj = {
433             sec: result.tv_sec,
434             usec: result.tv_usec
435         };
436         return resulObj;
437     }
438 
439 
440     version (LIBSSH_WITH_SERVER) {
441         void serverInit() {
442             auto rc = sftp_server_init(this._sftpSession);
443             if (rc != SSH_OK) {
444                 throw new SFTPException(sftp_get_error(this._sftpSession), this._session._session);
445             }
446         }
447     }
448 
449 
450     ~this() {
451         this._dispose(true);
452     }
453     
454     override void dispose() {
455         this._dispose(false);
456     }
457     
458     package {
459         this(SSHSession parent, sftp_session sftpSession) {
460             this._session = parent;
461             this._sftpSession = sftpSession;
462         }
463     }
464     
465     private {
466         void _dispose(bool fromDtor) {
467             if (this._sftpSession !is null) {
468                 sftp_free(this._sftpSession);
469                 this._session = null;
470                 this._sftpSession = null;
471             }
472         }
473         
474         SSHSession _session;
475         sftp_session _sftpSession;
476     }
477 }
478 
479 private {
480     string convertSSHStringToString(ssh_string s) {
481         auto dataPtr = ssh_string_data(s);
482         if (dataPtr is null) {
483             return null;
484         }
485 
486         auto len = ssh_string_len(s);
487         auto result = new char[len];
488         memcpy(result.ptr, dataPtr, len);
489         return cast(string) result;
490     }
491 
492     ssh_string convertStringToSSHString(string s) {
493         auto result = ssh_string_new(s.length);
494         if (result is null) {
495             return null;
496         }
497         if (ssh_string_fill(result, s.ptr, s.length) < 0) {
498             ssh_string_free(result);
499             return null;
500         }
501         return null;
502     }
503 
504     void convertAndFreeSftpAttributes(sftp_attributes attrs, out SFTPAttributes outAttrs) {
505         scope(exit) sftp_attributes_free(attrs);
506 
507         outAttrs.name = copyFromStrZ(attrs.name);
508         outAttrs.longName = copyFromStrZ(attrs.longname);
509         outAttrs.flags = attrs.flags;
510         outAttrs.type = attrs.type;
511         outAttrs.size = attrs.size;
512         outAttrs.uid = attrs.uid;
513         outAttrs.gid = attrs.gid;
514         outAttrs.owner = copyFromStrZ(attrs.owner);
515         outAttrs.group = copyFromStrZ(attrs.group);
516         outAttrs.permissions = attrs.permissions;
517         outAttrs.atime64 = attrs.atime64;
518         outAttrs.atime = attrs.atime;
519         outAttrs.atimeNSeconds = attrs.atime_nseconds;
520         outAttrs.createtime = attrs.createtime;
521         outAttrs.createtimeNSeconds = attrs.createtime_nseconds;
522         outAttrs.mtime64 = attrs.mtime64;
523         outAttrs.mtime = attrs.mtime;
524         outAttrs.mtimeNSeconds = attrs.mtime_nseconds;
525         outAttrs.acl = convertSSHStringToString(attrs.acl);
526         outAttrs.extendedCount = attrs.extended_count;
527         outAttrs.extendedType = convertSSHStringToString(attrs.extended_type);
528         outAttrs.extendedData = convertSSHStringToString(attrs.extended_data);
529     }
530 
531     void convertAndSetSftpAttributes(sftp_attributes outAttrs, SFTPAttributes attrs) {        
532         outAttrs.name = copyToStrZ(attrs.name);
533         outAttrs.longname = copyToStrZ(attrs.longName);
534         outAttrs.flags = attrs.flags;
535         outAttrs.type = attrs.type;
536         outAttrs.size = attrs.size;
537         outAttrs.uid = attrs.uid;
538         outAttrs.gid = attrs.gid;
539         outAttrs.owner = copyToStrZ(attrs.owner);
540         outAttrs.group = copyToStrZ(attrs.group);
541         outAttrs.permissions = attrs.permissions;
542         outAttrs.atime64 = attrs.atime64;
543         outAttrs.atime = attrs.atime;
544         outAttrs.atime_nseconds = attrs.atimeNSeconds;
545         outAttrs.createtime = attrs.createtime;
546         outAttrs.createtime_nseconds = attrs.createtimeNSeconds;
547         outAttrs.mtime64 = attrs.mtime64;
548         outAttrs.mtime = attrs.mtime;
549         outAttrs.mtime_nseconds = attrs.mtimeNSeconds;
550         outAttrs.acl = convertStringToSSHString(attrs.acl);
551         outAttrs.extended_count = attrs.extendedCount;
552         outAttrs.extended_type = convertStringToSSHString(attrs.extendedType);
553         outAttrs.extended_data = convertStringToSSHString(attrs.extendedData);
554     }
555 }