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 }