mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
Add link/linkSync fs call for hardlinks (#2074)
This commit is contained in:
parent
4232c89c9e
commit
1746a3ac69
7 changed files with 167 additions and 0 deletions
|
@ -72,6 +72,7 @@ ts_sources = [
|
||||||
"../js/headers.ts",
|
"../js/headers.ts",
|
||||||
"../js/io.ts",
|
"../js/io.ts",
|
||||||
"../js/lib.web_assembly.d.ts",
|
"../js/lib.web_assembly.d.ts",
|
||||||
|
"../js/link.ts",
|
||||||
"../js/location.ts",
|
"../js/location.ts",
|
||||||
"../js/main.ts",
|
"../js/main.ts",
|
||||||
"../js/make_temp_dir.ts",
|
"../js/make_temp_dir.ts",
|
||||||
|
|
|
@ -21,6 +21,7 @@ union Any {
|
||||||
GlobalTimerStop,
|
GlobalTimerStop,
|
||||||
IsTTY,
|
IsTTY,
|
||||||
IsTTYRes,
|
IsTTYRes,
|
||||||
|
Link,
|
||||||
Listen,
|
Listen,
|
||||||
ListenRes,
|
ListenRes,
|
||||||
MakeTempDir,
|
MakeTempDir,
|
||||||
|
@ -391,6 +392,11 @@ table Symlink {
|
||||||
newname: string;
|
newname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table Link {
|
||||||
|
oldname: string;
|
||||||
|
newname: string;
|
||||||
|
}
|
||||||
|
|
||||||
table Stat {
|
table Stat {
|
||||||
filename: string;
|
filename: string;
|
||||||
lstat: bool;
|
lstat: bool;
|
||||||
|
|
23
cli/ops.rs
23
cli/ops.rs
|
@ -176,6 +176,7 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option<OpCreator> {
|
||||||
msg::Any::GlobalTimer => Some(op_global_timer),
|
msg::Any::GlobalTimer => Some(op_global_timer),
|
||||||
msg::Any::GlobalTimerStop => Some(op_global_timer_stop),
|
msg::Any::GlobalTimerStop => Some(op_global_timer_stop),
|
||||||
msg::Any::IsTTY => Some(op_is_tty),
|
msg::Any::IsTTY => Some(op_is_tty),
|
||||||
|
msg::Any::Link => Some(op_link),
|
||||||
msg::Any::Listen => Some(op_listen),
|
msg::Any::Listen => Some(op_listen),
|
||||||
msg::Any::MakeTempDir => Some(op_make_temp_dir),
|
msg::Any::MakeTempDir => Some(op_make_temp_dir),
|
||||||
msg::Any::Metrics => Some(op_metrics),
|
msg::Any::Metrics => Some(op_metrics),
|
||||||
|
@ -1259,6 +1260,28 @@ fn op_rename(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn op_link(
|
||||||
|
sc: &IsolateStateContainer,
|
||||||
|
base: &msg::Base<'_>,
|
||||||
|
data: deno_buf,
|
||||||
|
) -> Box<OpWithError> {
|
||||||
|
assert_eq!(data.len(), 0);
|
||||||
|
let inner = base.inner_as_link().unwrap();
|
||||||
|
let oldname = PathBuf::from(inner.oldname().unwrap());
|
||||||
|
let newname_ = inner.newname().unwrap();
|
||||||
|
let newname = PathBuf::from(newname_);
|
||||||
|
|
||||||
|
if let Err(e) = sc.state().check_write(&newname_) {
|
||||||
|
return odd_future(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
blocking(base.sync(), move || -> OpResult {
|
||||||
|
debug!("op_link {} {}", oldname.display(), newname.display());
|
||||||
|
std::fs::hard_link(&oldname, &newname)?;
|
||||||
|
Ok(empty_buf())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn op_symlink(
|
fn op_symlink(
|
||||||
sc: &IsolateStateContainer,
|
sc: &IsolateStateContainer,
|
||||||
base: &msg::Base<'_>,
|
base: &msg::Base<'_>,
|
||||||
|
|
|
@ -53,6 +53,7 @@ export { readDirSync, readDir } from "./read_dir";
|
||||||
export { copyFileSync, copyFile } from "./copy_file";
|
export { copyFileSync, copyFile } from "./copy_file";
|
||||||
export { readlinkSync, readlink } from "./read_link";
|
export { readlinkSync, readlink } from "./read_link";
|
||||||
export { statSync, lstatSync, stat, lstat } from "./stat";
|
export { statSync, lstatSync, stat, lstat } from "./stat";
|
||||||
|
export { linkSync, link } from "./link";
|
||||||
export { symlinkSync, symlink } from "./symlink";
|
export { symlinkSync, symlink } from "./symlink";
|
||||||
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
|
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
|
||||||
export { ErrorKind, DenoError } from "./errors";
|
export { ErrorKind, DenoError } from "./errors";
|
||||||
|
|
31
js/link.ts
Normal file
31
js/link.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import * as msg from "gen/cli/msg_generated";
|
||||||
|
import * as flatbuffers from "./flatbuffers";
|
||||||
|
import * as dispatch from "./dispatch";
|
||||||
|
|
||||||
|
function req(
|
||||||
|
oldname: string,
|
||||||
|
newname: string
|
||||||
|
): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] {
|
||||||
|
const builder = flatbuffers.createBuilder();
|
||||||
|
const oldname_ = builder.createString(oldname);
|
||||||
|
const newname_ = builder.createString(newname);
|
||||||
|
const inner = msg.Link.createLink(builder, oldname_, newname_);
|
||||||
|
return [builder, msg.Any.Link, inner];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Synchronously creates `newname` as a hard link to `oldname`.
|
||||||
|
*
|
||||||
|
* Deno.linkSync("old/name", "new/name");
|
||||||
|
*/
|
||||||
|
export function linkSync(oldname: string, newname: string): void {
|
||||||
|
dispatch.sendSync(...req(oldname, newname));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates `newname` as a hard link to `oldname`.
|
||||||
|
*
|
||||||
|
* await Deno.link("old/name", "new/name");
|
||||||
|
*/
|
||||||
|
export async function link(oldname: string, newname: string): Promise<void> {
|
||||||
|
await dispatch.sendAsync(...req(oldname, newname));
|
||||||
|
}
|
104
js/link_test.ts
Normal file
104
js/link_test.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { test, testPerm, assert, assertEquals } from "./test_util.ts";
|
||||||
|
|
||||||
|
testPerm({ read: true, write: true }, function linkSyncSuccess() {
|
||||||
|
const testDir = Deno.makeTempDirSync();
|
||||||
|
const oldData = "Hardlink";
|
||||||
|
const oldName = testDir + "/oldname";
|
||||||
|
const newName = testDir + "/newname";
|
||||||
|
Deno.writeFileSync(oldName, new TextEncoder().encode(oldData));
|
||||||
|
// Create the hard link.
|
||||||
|
Deno.linkSync(oldName, newName);
|
||||||
|
// We should expect reading the same content.
|
||||||
|
const newData = new TextDecoder().decode(Deno.readFileSync(newName));
|
||||||
|
assertEquals(oldData, newData);
|
||||||
|
// Writing to newname also affects oldname.
|
||||||
|
const newData2 = "Modified";
|
||||||
|
Deno.writeFileSync(newName, new TextEncoder().encode(newData2));
|
||||||
|
assertEquals(newData2, new TextDecoder().decode(Deno.readFileSync(oldName)));
|
||||||
|
// Writing to oldname also affects newname.
|
||||||
|
const newData3 = "ModifiedAgain";
|
||||||
|
Deno.writeFileSync(oldName, new TextEncoder().encode(newData3));
|
||||||
|
assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
|
||||||
|
// Remove oldname. File still accessible through newname.
|
||||||
|
Deno.removeSync(oldName);
|
||||||
|
const newNameStat = Deno.statSync(newName);
|
||||||
|
assert(newNameStat.isFile());
|
||||||
|
assert(!newNameStat.isSymlink()); // Not a symlink.
|
||||||
|
assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ read: true, write: true }, function linkSyncExists() {
|
||||||
|
const testDir = Deno.makeTempDirSync();
|
||||||
|
const oldName = testDir + "/oldname";
|
||||||
|
const newName = testDir + "/newname";
|
||||||
|
Deno.writeFileSync(oldName, new TextEncoder().encode("oldName"));
|
||||||
|
// newname is already created.
|
||||||
|
Deno.writeFileSync(newName, new TextEncoder().encode("newName"));
|
||||||
|
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
Deno.linkSync(oldName, newName);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assert(!!err);
|
||||||
|
console.log(err);
|
||||||
|
assertEquals(err.kind, Deno.ErrorKind.AlreadyExists);
|
||||||
|
assertEquals(err.name, "AlreadyExists");
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ read: true, write: true }, function linkSyncNotFound() {
|
||||||
|
const testDir = Deno.makeTempDirSync();
|
||||||
|
const oldName = testDir + "/oldname";
|
||||||
|
const newName = testDir + "/newname";
|
||||||
|
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
Deno.linkSync(oldName, newName);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assert(!!err);
|
||||||
|
console.log(err);
|
||||||
|
assertEquals(err.kind, Deno.ErrorKind.NotFound);
|
||||||
|
assertEquals(err.name, "NotFound");
|
||||||
|
});
|
||||||
|
|
||||||
|
test(function linkSyncPerm() {
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
Deno.linkSync("oldbaddir", "newbaddir");
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assertEquals(err.kind, Deno.ErrorKind.PermissionDenied);
|
||||||
|
assertEquals(err.name, "PermissionDenied");
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ read: true, write: true }, async function linkSuccess() {
|
||||||
|
const testDir = Deno.makeTempDirSync();
|
||||||
|
const oldData = "Hardlink";
|
||||||
|
const oldName = testDir + "/oldname";
|
||||||
|
const newName = testDir + "/newname";
|
||||||
|
Deno.writeFileSync(oldName, new TextEncoder().encode(oldData));
|
||||||
|
// Create the hard link.
|
||||||
|
await Deno.link(oldName, newName);
|
||||||
|
// We should expect reading the same content.
|
||||||
|
const newData = new TextDecoder().decode(Deno.readFileSync(newName));
|
||||||
|
assertEquals(oldData, newData);
|
||||||
|
// Writing to newname also affects oldname.
|
||||||
|
const newData2 = "Modified";
|
||||||
|
Deno.writeFileSync(newName, new TextEncoder().encode(newData2));
|
||||||
|
assertEquals(newData2, new TextDecoder().decode(Deno.readFileSync(oldName)));
|
||||||
|
// Writing to oldname also affects newname.
|
||||||
|
const newData3 = "ModifiedAgain";
|
||||||
|
Deno.writeFileSync(oldName, new TextEncoder().encode(newData3));
|
||||||
|
assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
|
||||||
|
// Remove oldname. File still accessible through newname.
|
||||||
|
Deno.removeSync(oldName);
|
||||||
|
const newNameStat = Deno.statSync(newName);
|
||||||
|
assert(newNameStat.isFile());
|
||||||
|
assert(!newNameStat.isSymlink()); // Not a symlink.
|
||||||
|
assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
|
||||||
|
});
|
|
@ -23,6 +23,7 @@ import "./files_test.ts";
|
||||||
import "./form_data_test.ts";
|
import "./form_data_test.ts";
|
||||||
import "./globals_test.ts";
|
import "./globals_test.ts";
|
||||||
import "./headers_test.ts";
|
import "./headers_test.ts";
|
||||||
|
import "./link_test.ts";
|
||||||
import "./location_test.ts";
|
import "./location_test.ts";
|
||||||
import "./make_temp_dir_test.ts";
|
import "./make_temp_dir_test.ts";
|
||||||
import "./metrics_test.ts";
|
import "./metrics_test.ts";
|
||||||
|
|
Loading…
Reference in a new issue